Merge "Binder cache for Bluetooth getState()."
diff --git a/Android.bp b/Android.bp
index 5796fb1..df852bd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -263,8 +263,6 @@
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
         ":libupdate_engine_aidl",
-        // TODO: this needs to be removed when statsd-framework.jar is separated out
-        ":statsd_java_aidl",
         ":storaged_aidl",
         ":vold_aidl",
 
@@ -403,7 +401,6 @@
         "unsupportedappusage",
         "framework-media-stubs-systemapi",
         "framework-mediaprovider-stubs-systemapi",
-        "framework-tethering",
         "framework-telephony-stubs",
     ],
 
@@ -446,13 +443,6 @@
 }
 
 filegroup {
-    name: "graphicsstats_proto",
-    srcs: [
-        "libs/hwui/protos/graphicsstats.proto",
-    ],
-}
-
-filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
@@ -468,10 +458,11 @@
     libs: [
         "framework-appsearch-stubs",
         "framework-sdkextensions-stubs-systemapi",
-        "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs
+        "framework-statsd-stubs-module_libs_api",
         "framework-permission-stubs-systemapi",
         "framework-wifi-stubs",
         "ike-stubs",
+        "framework-tethering-stubs",
     ],
     installable: true,
     javac_shard_size: 150,
@@ -496,6 +487,7 @@
         "//frameworks/base/apex/blobstore/framework",
         "//frameworks/base/apex/jobscheduler/framework",
         "//frameworks/base/apex/statsd/service",
+        "//frameworks/base/packages/Tethering/tests/unit",
     ],
 }
 
@@ -519,12 +511,10 @@
         "framework-mediaprovider-stubs-systemapi",
         "framework-permission-stubs-systemapi",
         "framework-sdkextensions-stubs-systemapi",
-        // TODO(b/146167933): Use framework-statsd-stubs instead.
-        "framework-statsd",
+        "framework-statsd-stubs-module_libs_api",
         "framework-wifi-stubs",
         "ike-stubs",
-        // TODO(b/147200698): should be the stub of framework-tethering
-        "framework-tethering",
+        "framework-tethering-stubs",
         // TODO (b/147688669) should be framework-telephony-stubs
         "framework-telephony",
         // TODO(jiyong): add stubs for APEXes here
@@ -625,6 +615,18 @@
     out: ["com/android/internal/util/FrameworkStatsLog.java"],
 }
 
+java_library {
+    name: "uieventloggerlib",
+    srcs: [
+        "core/java/com/android/internal/logging/UiEvent.java",
+        "core/java/com/android/internal/logging/UiEventLogger.java",
+        "core/java/com/android/internal/logging/UiEventLoggerImpl.java",
+        "core/java/com/android/internal/logging/InstanceId.java",
+        "core/java/com/android/internal/logging/InstanceIdSequence.java",
+        ":statslog-framework-java-gen",
+    ],
+}
+
 gensrcs {
     name: "framework-javastream-protos",
     depfile: true,
@@ -653,6 +655,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: [
@@ -1010,43 +1039,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/StubLibraries.bp b/StubLibraries.bp
index d4db737..9b9311f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -190,7 +190,6 @@
 droidstubs {
     name: "module-lib-api",
     defaults: ["metalava-api-stubs-default"],
-    libs: ["framework-all"],
     arg_files: ["core/res/AndroidManifest.xml"],
     args: metalava_framework_docs_args + module_libs,
     check_api: {
@@ -222,7 +221,6 @@
 droidstubs {
     name: "module-lib-api-stubs-docs",
     defaults: ["metalava-api-stubs-default"],
-    libs: ["framework-all"],
     arg_files: ["core/res/AndroidManifest.xml"],
     args: metalava_framework_docs_args + priv_apps + module_libs,
 }
@@ -299,12 +297,12 @@
     ],
     libs: [
         "stub-annotations",
-        "framework-all",
     ],
     static_libs: [
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 /////////////////////////////////////////////////////////////////////
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/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
deleted file mode 100644
index 236f548..0000000
--- a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2017 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.os;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class PackageManagerPerfTest {
-    private static final String PERMISSION_NAME_EXISTS =
-            "com.android.perftests.core.TestPermission";
-    private static final String PERMISSION_NAME_DOESNT_EXIST =
-            "com.android.perftests.core.TestBadPermission";
-    private static final ComponentName TEST_ACTIVITY =
-            new ComponentName("com.android.perftests.core",
-                    "android.perftests.utils.PerfTestActivity");
-
-    @Rule
-    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
-
-    @Test
-    public void testCheckPermissionExists() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        final String packageName = TEST_ACTIVITY.getPackageName();
-
-        while (state.keepRunning()) {
-            int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
-        }
-    }
-
-    @Test
-    public void testCheckPermissionDoesntExist() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        final String packageName = TEST_ACTIVITY.getPackageName();
-
-        while (state.keepRunning()) {
-            int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
-        }
-    }
-
-    @Test
-    public void testQueryIntentActivities() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
-
-        while (state.keepRunning()) {
-            pm.queryIntentActivities(intent, 0);
-        }
-    }
-
-    @Test
-    public void testGetPackageInfo() throws Exception {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        final String packageName = TEST_ACTIVITY.getPackageName();
-
-        while (state.keepRunning()) {
-            pm.getPackageInfo(packageName, 0);
-        }
-    }
-
-    @Test
-    public void testGetApplicationInfo() throws Exception {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        final String packageName = TEST_ACTIVITY.getPackageName();
-        
-        while (state.keepRunning()) {
-            pm.getApplicationInfo(packageName, 0);
-        }
-    }
-
-    @Test
-    public void testGetActivityInfo() throws Exception {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        
-        while (state.keepRunning()) {
-            pm.getActivityInfo(TEST_ACTIVITY, 0);
-        }
-    }
-}
diff --git a/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java
new file mode 100644
index 0000000..14282bf
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java
@@ -0,0 +1,240 @@
+/*
+ * 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.view;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.PathParser;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CutoutSpecificationBenchmark {
+    private static final String TAG = "CutoutSpecificationBenchmark";
+
+    private static final String BOTTOM_MARKER = "@bottom";
+    private static final String DP_MARKER = "@dp";
+    private static final String RIGHT_MARKER = "@right";
+    private static final String LEFT_MARKER = "@left";
+
+    private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n"
+            + "L -72, 0\n"
+            + "L -69.9940446283, 20.0595537175\n"
+            + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+            + "L 56.8, 32.0\n"
+            + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+            + "L 72, 0\n"
+            + "Z\n"
+            + "@bottom\n"
+            + "M 0,0\n"
+            + "L -72, 0\n"
+            + "L -69.9940446283, -20.0595537175\n"
+            + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+            + "L 56.8, -32.0\n"
+            + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n"
+            + "L 72, 0\n"
+            + "Z\n"
+            + "@dp";
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private Context mContext;
+    private DisplayMetrics mDisplayMetrics;
+
+    /**
+     * Setup the necessary member field used by test methods.
+     */
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mDisplayMetrics = new DisplayMetrics();
+        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+    }
+
+
+    private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+        final RectF rectF = new RectF();
+        p.computeBounds(rectF, false /* unused */);
+        rectF.round(inoutRect);
+        inoutRegion.op(inoutRect, Region.Op.UNION);
+    }
+
+    private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight,
+            float density) {
+        Path p = null;
+        Rect boundTop = null;
+        Rect boundBottom = null;
+        Rect safeInset = new Rect();
+        String bottomSpec = null;
+        if (!TextUtils.isEmpty(spec)) {
+            spec = spec.trim();
+            final float offsetX;
+            if (spec.endsWith(RIGHT_MARKER)) {
+                offsetX = displayWidth;
+                spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+            } else if (spec.endsWith(LEFT_MARKER)) {
+                offsetX = 0;
+                spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
+            } else {
+                offsetX = displayWidth / 2f;
+            }
+            final boolean inDp = spec.endsWith(DP_MARKER);
+            if (inDp) {
+                spec = spec.substring(0, spec.length() - DP_MARKER.length());
+            }
+
+            if (spec.contains(BOTTOM_MARKER)) {
+                String[] splits = spec.split(BOTTOM_MARKER, 2);
+                spec = splits[0].trim();
+                bottomSpec = splits[1].trim();
+            }
+
+            final Matrix m = new Matrix();
+            final Region r = Region.obtain();
+            if (!spec.isEmpty()) {
+                try {
+                    p = PathParser.createPathFromPathData(spec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate cutout: ", e);
+                }
+
+                if (p != null) {
+                    if (inDp) {
+                        m.postScale(density, density);
+                    }
+                    m.postTranslate(offsetX, 0);
+                    p.transform(m);
+
+                    boundTop = new Rect();
+                    toRectAndAddToRegion(p, r, boundTop);
+                    safeInset.top = boundTop.bottom;
+                }
+            }
+
+            if (bottomSpec != null) {
+                int bottomInset = 0;
+                Path bottomPath = null;
+                try {
+                    bottomPath = PathParser.createPathFromPathData(bottomSpec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
+                }
+
+                if (bottomPath != null) {
+                    // Keep top transform
+                    m.postTranslate(0, displayHeight);
+                    bottomPath.transform(m);
+                    p.addPath(bottomPath);
+                    boundBottom = new Rect();
+                    toRectAndAddToRegion(bottomPath, r, boundBottom);
+                    bottomInset = displayHeight - boundBottom.top;
+                }
+                safeInset.bottom = bottomInset;
+            }
+        }
+    }
+
+    @Test
+    public void parseByOldMethodForDoubleCutout() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels,
+                    mDisplayMetrics.heightPixels, mDisplayMetrics.density);
+        }
+    }
+
+    @Test
+    public void parseByNewMethodForDoubleCutout() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(mDisplayMetrics.density,
+                    mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)
+                    .parse(DOUBLE_CUTOUT_SPEC);
+        }
+    }
+
+    @Test
+    public void parseLongEdgeCutout() {
+        final String spec = "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@left\n"
+                + "@center_vertical\n"
+                + "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@left\n"
+                + "@center_vertical\n"
+                + "M 0,0\n"
+                + "H -48\n"
+                + "V 48\n"
+                + "H 48\n"
+                + "Z\n"
+                + "@right\n"
+                + "@dp";
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(mDisplayMetrics.density,
+                    mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
+        }
+    }
+
+    @Test
+    public void parseShortEdgeCutout() {
+        final String spec = "M 0,0\n"
+                + "H 48\n"
+                + "V 48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@bottom\n"
+                + "M 0,0\n"
+                + "H 48\n"
+                + "V -48\n"
+                + "H -48\n"
+                + "Z\n"
+                + "@dp";
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            new CutoutSpecification.Parser(mDisplayMetrics.density,
+                    mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index b6e39e1..8633c96 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -125,7 +125,9 @@
         final WindowManager.LayoutParams mParams;
         final int mWidth;
         final int mHeight;
+        final Point mOutSurfaceSize = new Point();
         final SurfaceControl mOutSurfaceControl;
+        final SurfaceControl mOutBlastSurfaceControl = new SurfaceControl();
 
         final IntSupplier mViewVisibility;
 
@@ -150,7 +152,8 @@
                         mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame,
                         mOutContentInsets, mOutVisibleInsets, mOutStableInsets,
                         mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration,
-                        mOutSurfaceControl, mOutInsetsState, new Point(), new SurfaceControl());
+                        mOutSurfaceControl, mOutInsetsState, mOutSurfaceSize,
+                        mOutBlastSurfaceControl);
             }
         }
     }
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
index 62e9ba8..9e17e94 100644
--- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -20,15 +20,19 @@
 
 import android.app.Activity;
 import android.app.UiAutomation;
+import android.content.Context;
 import android.content.Intent;
+import android.os.BatteryManager;
 import android.os.ParcelFileDescriptor;
 import android.perftests.utils.PerfTestActivity;
+import android.provider.Settings;
 
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import androidx.test.runner.lifecycle.Stage;
 
+import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -52,18 +56,36 @@
      */
     static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests");
 
+    private static int sOriginalStayOnWhilePluggedIn;
+
     @BeforeClass
     public static void setUpOnce() {
+        final Context context = getInstrumentation().getContext();
+        sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+        // Keep the device awake during testing.
+        setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB);
+
         if (!BASE_OUT_PATH.exists()) {
             executeShellCommand("mkdir -p " + BASE_OUT_PATH);
         }
         // In order to be closer to the real use case.
         executeShellCommand("input keyevent KEYCODE_WAKEUP");
         executeShellCommand("wm dismiss-keyguard");
-        getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+        context.startActivity(new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
     }
 
+    @AfterClass
+    public static void tearDownOnce() {
+        setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
+    }
+
+    private static void setStayOnWhilePluggedIn(int value) {
+        executeShellCommand(String.format("settings put global %s %d",
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
+    }
+
     /**
      * Executes shell command with reading the output. It may also used to block until the current
      * command is completed.
@@ -97,7 +119,7 @@
      */
     static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
         private final Intent mStartIntent =
-                new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true);
+                new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class);
         private final LifecycleListener mLifecycleListener = new LifecycleListener();
 
         PerfTestActivityRule() {
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
new file mode 100644
index 0000000..17033e0
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -0,0 +1,21 @@
+android_test {
+    name: "PackageManagerPerfTests",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "platform-compat-test-rules",
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.annotation_annotation",
+        "apct-perftests-utils",
+    ],
+
+    libs: ["android.test.base"],
+
+    platform_apis: true,
+
+    test_suites: ["device-tests"],
+
+}
diff --git a/apct-tests/perftests/packagemanager/AndroidManifest.xml b/apct-tests/perftests/packagemanager/AndroidManifest.xml
new file mode 100644
index 0000000..520f4b5
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/AndroidManifest.xml
@@ -0,0 +1,89 @@
+<?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.packagemanager">
+
+    <permission android:name="com.android.perftests.packagemanager.TestPermission" />
+    <uses-permission android:name="com.android.perftests.packagemanager.TestPermission" />
+
+    <queries>
+        <package android:name="com.android.perftests.appenumeration0" />
+        <package android:name="com.android.perftests.appenumeration1" />
+        <package android:name="com.android.perftests.appenumeration2" />
+        <package android:name="com.android.perftests.appenumeration3" />
+        <package android:name="com.android.perftests.appenumeration4" />
+        <package android:name="com.android.perftests.appenumeration5" />
+        <package android:name="com.android.perftests.appenumeration6" />
+        <package android:name="com.android.perftests.appenumeration7" />
+        <package android:name="com.android.perftests.appenumeration8" />
+        <package android:name="com.android.perftests.appenumeration9" />
+        <package android:name="com.android.perftests.appenumeration10" />
+        <package android:name="com.android.perftests.appenumeration11" />
+        <package android:name="com.android.perftests.appenumeration12" />
+        <package android:name="com.android.perftests.appenumeration13" />
+        <package android:name="com.android.perftests.appenumeration14" />
+        <package android:name="com.android.perftests.appenumeration15" />
+        <package android:name="com.android.perftests.appenumeration16" />
+        <package android:name="com.android.perftests.appenumeration17" />
+        <package android:name="com.android.perftests.appenumeration18" />
+        <package android:name="com.android.perftests.appenumeration19" />
+        <package android:name="com.android.perftests.appenumeration20" />
+        <package android:name="com.android.perftests.appenumeration21" />
+        <package android:name="com.android.perftests.appenumeration22" />
+        <package android:name="com.android.perftests.appenumeration23" />
+        <package android:name="com.android.perftests.appenumeration24" />
+        <package android:name="com.android.perftests.appenumeration25" />
+        <package android:name="com.android.perftests.appenumeration26" />
+        <package android:name="com.android.perftests.appenumeration27" />
+        <package android:name="com.android.perftests.appenumeration28" />
+        <package android:name="com.android.perftests.appenumeration29" />
+        <package android:name="com.android.perftests.appenumeration30" />
+        <package android:name="com.android.perftests.appenumeration31" />
+        <package android:name="com.android.perftests.appenumeration32" />
+        <package android:name="com.android.perftests.appenumeration33" />
+        <package android:name="com.android.perftests.appenumeration34" />
+        <package android:name="com.android.perftests.appenumeration35" />
+        <package android:name="com.android.perftests.appenumeration36" />
+        <package android:name="com.android.perftests.appenumeration37" />
+        <package android:name="com.android.perftests.appenumeration38" />
+        <package android:name="com.android.perftests.appenumeration39" />
+        <package android:name="com.android.perftests.appenumeration40" />
+        <package android:name="com.android.perftests.appenumeration41" />
+        <package android:name="com.android.perftests.appenumeration42" />
+        <package android:name="com.android.perftests.appenumeration43" />
+        <package android:name="com.android.perftests.appenumeration44" />
+        <package android:name="com.android.perftests.appenumeration45" />
+        <package android:name="com.android.perftests.appenumeration46" />
+        <package android:name="com.android.perftests.appenumeration47" />
+        <package android:name="com.android.perftests.appenumeration48" />
+        <package android:name="com.android.perftests.appenumeration49" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.perftests.utils.PerfTestActivity">
+          <intent-filter>
+            <action android:name="com.android.perftests.packagemanager.PERFTEST" />
+          </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.perftests.packagemanager"/>
+
+</manifest>
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml
new file mode 100644
index 0000000..c112d87
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/AndroidTest.xml
@@ -0,0 +1,88 @@
+<?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 PackageManagerPerfTests 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="PackageManagerPerfTests.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="force-queryable" value="false" />
+        <option name="test-file-name" value="QueriesAll0.apk" />
+        <option name="test-file-name" value="QueriesAll1.apk" />
+        <option name="test-file-name" value="QueriesAll2.apk" />
+        <option name="test-file-name" value="QueriesAll3.apk" />
+        <option name="test-file-name" value="QueriesAll4.apk" />
+        <option name="test-file-name" value="QueriesAll5.apk" />
+        <option name="test-file-name" value="QueriesAll6.apk" />
+        <option name="test-file-name" value="QueriesAll7.apk" />
+        <option name="test-file-name" value="QueriesAll8.apk" />
+        <option name="test-file-name" value="QueriesAll9.apk" />
+        <option name="test-file-name" value="QueriesAll10.apk" />
+        <option name="test-file-name" value="QueriesAll11.apk" />
+        <option name="test-file-name" value="QueriesAll12.apk" />
+        <option name="test-file-name" value="QueriesAll13.apk" />
+        <option name="test-file-name" value="QueriesAll14.apk" />
+        <option name="test-file-name" value="QueriesAll15.apk" />
+        <option name="test-file-name" value="QueriesAll16.apk" />
+        <option name="test-file-name" value="QueriesAll17.apk" />
+        <option name="test-file-name" value="QueriesAll18.apk" />
+        <option name="test-file-name" value="QueriesAll19.apk" />
+        <option name="test-file-name" value="QueriesAll20.apk" />
+        <option name="test-file-name" value="QueriesAll21.apk" />
+        <option name="test-file-name" value="QueriesAll22.apk" />
+        <option name="test-file-name" value="QueriesAll23.apk" />
+        <option name="test-file-name" value="QueriesAll24.apk" />
+        <option name="test-file-name" value="QueriesAll25.apk" />
+        <option name="test-file-name" value="QueriesAll26.apk" />
+        <option name="test-file-name" value="QueriesAll27.apk" />
+        <option name="test-file-name" value="QueriesAll28.apk" />
+        <option name="test-file-name" value="QueriesAll29.apk" />
+        <option name="test-file-name" value="QueriesAll30.apk" />
+        <option name="test-file-name" value="QueriesAll31.apk" />
+        <option name="test-file-name" value="QueriesAll32.apk" />
+        <option name="test-file-name" value="QueriesAll33.apk" />
+        <option name="test-file-name" value="QueriesAll34.apk" />
+        <option name="test-file-name" value="QueriesAll35.apk" />
+        <option name="test-file-name" value="QueriesAll36.apk" />
+        <option name="test-file-name" value="QueriesAll37.apk" />
+        <option name="test-file-name" value="QueriesAll38.apk" />
+        <option name="test-file-name" value="QueriesAll39.apk" />
+        <option name="test-file-name" value="QueriesAll40.apk" />
+        <option name="test-file-name" value="QueriesAll41.apk" />
+        <option name="test-file-name" value="QueriesAll42.apk" />
+        <option name="test-file-name" value="QueriesAll43.apk" />
+        <option name="test-file-name" value="QueriesAll44.apk" />
+        <option name="test-file-name" value="QueriesAll45.apk" />
+        <option name="test-file-name" value="QueriesAll46.apk" />
+        <option name="test-file-name" value="QueriesAll47.apk" />
+        <option name="test-file-name" value="QueriesAll48.apk" />
+        <option name="test-file-name" value="QueriesAll49.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.perftests.packagemanager" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/PackageManagerPerfTests" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/apct-tests/perftests/packagemanager/apps/query-all/Android.bp b/apct-tests/perftests/packagemanager/apps/query-all/Android.bp
new file mode 100644
index 0000000..3cb1589
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/apps/query-all/Android.bp
@@ -0,0 +1,314 @@
+// 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_helper_app {
+    name: "QueriesAll0",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration0",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll1",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration1",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll2",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration2",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll3",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration3",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll4",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration4",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll5",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration5",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll6",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration6",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll7",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration7",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll8",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration8",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll9",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration9",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll10",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration10",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll11",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration11",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll12",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration12",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll13",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration13",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll14",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration14",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll15",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration15",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll16",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration16",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll17",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration17",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll18",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration18",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll19",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration19",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll20",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration20",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll21",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration21",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll22",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration22",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll23",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration23",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll24",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration24",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll25",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration25",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll26",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration26",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll27",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration27",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll28",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration28",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll29",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration29",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll30",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration30",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll31",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration31",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll32",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration32",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll33",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration33",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll34",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration34",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll35",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration35",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll36",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration36",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll37",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration37",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll38",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration38",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll39",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration39",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll40",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration40",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll41",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration41",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll42",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration42",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll43",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration43",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll44",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration44",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll45",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration45",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll46",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration46",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll47",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration47",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll48",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration48",
+    ]
+}
+android_test_helper_app {
+    name: "QueriesAll49",
+    aaptflags: [
+        "--rename-manifest-package com.android.perftests.appenumeration49",
+    ]
+}
diff --git a/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml b/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml
new file mode 100644
index 0000000..e2cfa04
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml
@@ -0,0 +1,81 @@
+<?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.appenumeration">
+
+    <application android:hasCode="false" >
+        <activity android:name="android.perftests.utils.PerfTestActivity">
+            <intent-filter>
+                <action android:name="com.android.perftests.packagemanager.PERFTEST" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <queries>
+        <package android:name="com.android.perftests.appenumeration0" />
+        <package android:name="com.android.perftests.appenumeration1" />
+        <package android:name="com.android.perftests.appenumeration2" />
+        <package android:name="com.android.perftests.appenumeration3" />
+        <package android:name="com.android.perftests.appenumeration4" />
+        <package android:name="com.android.perftests.appenumeration5" />
+        <package android:name="com.android.perftests.appenumeration6" />
+        <package android:name="com.android.perftests.appenumeration7" />
+        <package android:name="com.android.perftests.appenumeration8" />
+        <package android:name="com.android.perftests.appenumeration9" />
+        <package android:name="com.android.perftests.appenumeration10" />
+        <package android:name="com.android.perftests.appenumeration11" />
+        <package android:name="com.android.perftests.appenumeration12" />
+        <package android:name="com.android.perftests.appenumeration13" />
+        <package android:name="com.android.perftests.appenumeration14" />
+        <package android:name="com.android.perftests.appenumeration15" />
+        <package android:name="com.android.perftests.appenumeration16" />
+        <package android:name="com.android.perftests.appenumeration17" />
+        <package android:name="com.android.perftests.appenumeration18" />
+        <package android:name="com.android.perftests.appenumeration19" />
+        <package android:name="com.android.perftests.appenumeration20" />
+        <package android:name="com.android.perftests.appenumeration21" />
+        <package android:name="com.android.perftests.appenumeration22" />
+        <package android:name="com.android.perftests.appenumeration23" />
+        <package android:name="com.android.perftests.appenumeration24" />
+        <package android:name="com.android.perftests.appenumeration25" />
+        <package android:name="com.android.perftests.appenumeration26" />
+        <package android:name="com.android.perftests.appenumeration27" />
+        <package android:name="com.android.perftests.appenumeration28" />
+        <package android:name="com.android.perftests.appenumeration29" />
+        <package android:name="com.android.perftests.appenumeration30" />
+        <package android:name="com.android.perftests.appenumeration31" />
+        <package android:name="com.android.perftests.appenumeration32" />
+        <package android:name="com.android.perftests.appenumeration33" />
+        <package android:name="com.android.perftests.appenumeration34" />
+        <package android:name="com.android.perftests.appenumeration35" />
+        <package android:name="com.android.perftests.appenumeration36" />
+        <package android:name="com.android.perftests.appenumeration37" />
+        <package android:name="com.android.perftests.appenumeration38" />
+        <package android:name="com.android.perftests.appenumeration39" />
+        <package android:name="com.android.perftests.appenumeration40" />
+        <package android:name="com.android.perftests.appenumeration41" />
+        <package android:name="com.android.perftests.appenumeration42" />
+        <package android:name="com.android.perftests.appenumeration43" />
+        <package android:name="com.android.perftests.appenumeration44" />
+        <package android:name="com.android.perftests.appenumeration45" />
+        <package android:name="com.android.perftests.appenumeration46" />
+        <package android:name="com.android.perftests.appenumeration47" />
+        <package android:name="com.android.perftests.appenumeration48" />
+        <package android:name="com.android.perftests.appenumeration49" />
+    </queries>
+
+</manifest>
\ No newline at end of file
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
new file mode 100644
index 0000000..d7428cf
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.os;
+
+import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PackageManagerPerfTest {
+    private static final String PERMISSION_NAME_EXISTS =
+            "com.android.perftests.packagemanager.TestPermission";
+    private static final String PERMISSION_NAME_DOESNT_EXIST =
+            "com.android.perftests.packagemanager.TestBadPermission";
+    private static final String OTHER_PACKAGE_NAME = "com.android.perftests.appenumeration0";
+    private static final ComponentName TEST_ACTIVITY =
+            new ComponentName(OTHER_PACKAGE_NAME,
+                    "android.perftests.utils.PerfTestActivity");
+
+    @Rule
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Rule
+    public final PlatformCompatChangeRule mPlatformCompatChangeRule =
+            new PlatformCompatChangeRule();
+
+    public PackageManagerPerfTest() throws PackageManager.NameNotFoundException {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testCheckPermissionExists() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+
+        while (state.keepRunning()) {
+            int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testCheckPermissionExistsWithFiltering() {
+        testCheckPermissionExists();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testCheckPermissionDoesntExist() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+
+        while (state.keepRunning()) {
+            int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testCheckPermissionDoesntExistWithFiltering() {
+        testCheckPermissionDoesntExist();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testQueryIntentActivities() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
+
+        while (state.keepRunning()) {
+            pm.queryIntentActivities(intent, 0);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testQueryIntentActivitiesWithFiltering() {
+        testQueryIntentActivities();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetPackageInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+        while (state.keepRunning()) {
+            pm.getPackageInfo(OTHER_PACKAGE_NAME, 0);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetPackageInfoWithFiltering() throws Exception {
+        testGetPackageInfo();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetApplicationInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+        while (state.keepRunning()) {
+            pm.getApplicationInfo(OTHER_PACKAGE_NAME, 0);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetApplicationInfoWithFiltering() throws Exception {
+        testGetApplicationInfo();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetActivityInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+        while (state.keepRunning()) {
+            pm.getActivityInfo(TEST_ACTIVITY, 0);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetActivityInfoWithFiltering() throws Exception {
+        testGetActivityInfo();
+    }
+
+    @Test
+    @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetInstalledPackages() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+        while (state.keepRunning()) {
+            pm.getInstalledPackages(0);
+        }
+    }
+
+    @Test
+    @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+    public void testGetInstalledPackagesWithFiltering() throws Exception {
+        testGetInstalledPackages();
+    }
+}
diff --git a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java
index bd3b673..f61ea85 100644
--- a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java
+++ b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java
@@ -18,35 +18,60 @@
 import android.content.Context;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.SettingsHelper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 
 @LargeTest
 public class TextClassificationManagerPerfTest {
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
+    private String mOriginalSystemTextclassifierStatus;
+
+    @BeforeClass
+    public static void setUpClass() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        WRITE_DEVICE_CONFIG_PERMISSION);
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Before
+    public void setUp() {
+        // Saves config original value.
+        mOriginalSystemTextclassifierStatus = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER, "system_textclassifier_enabled");
+    }
+
     @After
     public void tearDown() {
-        SettingsHelper.delete(
-                SettingsHelper.NAMESPACE_GLOBAL, Settings.Global.TEXT_CLASSIFIER_CONSTANTS);
+        // Restores config original value.
+        enableSystemTextclassifier(mOriginalSystemTextclassifierStatus);
     }
 
     @Test
     public void testGetTextClassifier_systemTextClassifierDisabled() {
         Context context = InstrumentationRegistry.getTargetContext();
-        SettingsHelper.set(
-                SettingsHelper.NAMESPACE_GLOBAL,
-                Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
-                "system_textclassifier_enabled=false");
+        enableSystemTextclassifier(String.valueOf(false));
         TextClassificationManager textClassificationManager =
                 context.getSystemService(TextClassificationManager.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -59,10 +84,7 @@
     @Test
     public void testGetTextClassifier_systemTextClassifierEnabled() {
         Context context = InstrumentationRegistry.getTargetContext();
-        SettingsHelper.set(
-                SettingsHelper.NAMESPACE_GLOBAL,
-                Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
-                "system_textclassifier_enabled=true");
+        enableSystemTextclassifier(String.valueOf(true));
         TextClassificationManager textClassificationManager =
                 context.getSystemService(TextClassificationManager.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -71,4 +93,9 @@
             textClassificationManager.invalidateForTesting();
         }
     }
+
+    private void enableSystemTextclassifier(String enabled) {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "system_textclassifier_enabled", enabled, /* makeDefault */ false);
+    }
 }
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/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 69f4748..939164e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -494,9 +494,10 @@
         private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
         private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
 
-        private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT
+        private static final String DEPRECATED_KEY_MAX_STANDARD_RESCHEDULE_COUNT
                 = "max_standard_reschedule_count";
-        private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
+        private static final String DEPRECATED_KEY_MAX_WORK_RESCHEDULE_COUNT =
+                "max_work_reschedule_count";
         private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
         private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
         private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME =
@@ -525,8 +526,6 @@
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
         private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
-        private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE;
-        private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
@@ -640,16 +639,6 @@
                         "screen_off_job_concurrency_increase_delay_ms", 30_000);
 
         /**
-         * The maximum number of times we allow a job to have itself rescheduled before
-         * giving up on it, for standard jobs.
-         */
-        int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT;
-        /**
-         * The maximum number of times we allow a job to have itself rescheduled before
-         * giving up on it, for jobs that are executing work.
-         */
-        int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT;
-        /**
          * The minimum backoff time to allow for linear backoff.
          */
         long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME;
@@ -735,10 +724,6 @@
 
             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser);
 
-            MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
-                    DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
-            MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
-                    DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
             MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
                     DEFAULT_MIN_LINEAR_BACKOFF_TIME);
             MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
@@ -790,8 +775,6 @@
 
             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
 
-            pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
-            pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
             pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
             pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
             pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
@@ -827,8 +810,6 @@
             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
                     ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
 
-            proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
-            proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
             proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
@@ -1390,18 +1371,8 @@
                     // Effective standby bucket can change after this in some situations so use
                     // the real bucket so that the job is tracked by the controllers.
                     if (js.getStandbyBucket() == RESTRICTED_INDEX) {
-                        js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
-                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
-                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
-                        js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
-
                         mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js);
                     } else {
-                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
-                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
-                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
-                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
-
                         mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js);
                     }
                 }
@@ -1738,19 +1709,6 @@
         final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
         long delayMillis;
 
-        if (failureToReschedule.hasWorkLocked()) {
-            if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) {
-                Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
-                        + backoffAttempts + " > work limit "
-                        + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
-                return null;
-            }
-        } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) {
-            Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
-                    + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
-            return null;
-        }
-
         switch (job.getBackoffPolicy()) {
             case JobInfo.BACKOFF_POLICY_LINEAR: {
                 long backoff = initialBackoffMillis;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index f706260..1e89158 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -17,6 +17,8 @@
 package com.android.server.job.controllers;
 
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.app.AppGlobals;
@@ -63,26 +65,36 @@
  * @hide
  */
 public final class JobStatus {
-    static final String TAG = "JobSchedulerService";
+    private static final String TAG = "JobScheduler.JobStatus";
     static final boolean DEBUG = JobSchedulerService.DEBUG;
 
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
-    public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
-    public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
-    public static final int CONSTRAINT_BATTERY_NOT_LOW =
-            JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
+    static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
+    static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
+    static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     static final int CONSTRAINT_DEADLINE = 1<<30;
-    public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
+    static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
 
     /**
+     * The additional set of dynamic constraints that must be met if the job's effective bucket is
+     * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't
+     * need network.
+     */
+    private static final int DYNAMIC_RESTRICTED_CONSTRAINTS =
+            CONSTRAINT_BATTERY_NOT_LOW
+                    | CONSTRAINT_CHARGING
+                    | CONSTRAINT_CONNECTIVITY
+                    | CONSTRAINT_IDLE;
+
+    /**
      * The constraints that we want to log to statsd.
      *
      * Constraints that can be inferred from other atoms have been excluded to avoid logging too
@@ -419,7 +431,11 @@
         this.requiredConstraints = requiredConstraints;
         mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
         mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-        mReadyDynamicSatisfied = true;
+        if (standbyBucket == RESTRICTED_INDEX) {
+            addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
+        } else {
+            mReadyDynamicSatisfied = true;
+        }
 
         mLastSuccessfulRunTime = lastSuccessfulRunTime;
         mLastFailedRunTime = lastFailedRunTime;
@@ -727,6 +743,14 @@
     }
 
     public void setStandbyBucket(int newBucket) {
+        if (newBucket == RESTRICTED_INDEX) {
+            // Adding to the bucket.
+            addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
+        } else if (standbyBucket == RESTRICTED_INDEX) {
+            // Removing from the RESTRICTED bucket.
+            removeDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
+        }
+
         standbyBucket = newBucket;
     }
 
@@ -1054,6 +1078,11 @@
         if (old == state) {
             return false;
         }
+        if (DEBUG) {
+            Slog.v(TAG,
+                    "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
+                            + toShortString());
+        }
         satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
         mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
         mReadyDynamicSatisfied =
@@ -1086,38 +1115,40 @@
     }
 
     /**
-     * Indicates that this job cannot run without the specified constraint. This is evaluated
+     * Indicates that this job cannot run without the specified constraints. This is evaluated
      * separately from the job's explicitly requested constraints and MUST be satisfied before
      * the job can run if the app doesn't have quota.
-     *
      */
-    public void addDynamicConstraint(int constraint) {
-        if (constraint == CONSTRAINT_WITHIN_QUOTA) {
+    private void addDynamicConstraints(int constraints) {
+        if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
+            // Quota should never be used as a dynamic constraint.
             Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
-            return;
+            constraints &= ~CONSTRAINT_WITHIN_QUOTA;
         }
 
         // Connectivity and content trigger are special since they're only valid to add if the
         // job has requested network or specific content URIs. Adding these constraints to jobs
         // that don't need them doesn't make sense.
-        if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint())
-                || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) {
-            return;
+        if (!hasConnectivityConstraint()) {
+            constraints &= ~CONSTRAINT_CONNECTIVITY;
+        }
+        if (!hasContentTriggerConstraint()) {
+            constraints &= ~CONSTRAINT_CONTENT_TRIGGER;
         }
 
-        mDynamicConstraints |= constraint;
+        mDynamicConstraints |= constraints;
         mReadyDynamicSatisfied =
                 mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
     }
 
     /**
-     * Removes a dynamic constraint from a job, meaning that the requirement is not required for
+     * Removes dynamic constraints from a job, meaning that the requirements are not required for
      * the job to run (if the job itself hasn't requested the constraint. This is separate from
      * the job's explicitly requested constraints and does not remove those requested constraints.
      *
      */
-    public void removeDynamicConstraint(int constraint) {
-        mDynamicConstraints &= ~constraint;
+    private void removeDynamicConstraints(int constraints) {
+        mDynamicConstraints &= ~constraints;
         mReadyDynamicSatisfied =
                 mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
     }
@@ -1193,7 +1224,11 @@
 
     private boolean isReady(int satisfiedConstraints) {
         // Quota and dynamic constraints trump all other constraints.
-        if (!mReadyWithinQuota && !mReadyDynamicSatisfied) {
+        // NEVER jobs are not supposed to run at all. Since we're using quota to allow parole
+        // sessions (exempt from dynamic restrictions), we need the additional check to ensure
+        // that NEVER jobs don't run.
+        // TODO: cleanup quota and standby bucket management so we don't need the additional checks
+        if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) {
             return false;
         }
         // Deadline constraint trumps other constraints besides quota and dynamic (except for
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/permission/testing/Android.bp b/apex/permission/testing/Android.bp
index f8978dc..63bf0a0 100644
--- a/apex/permission/testing/Android.bp
+++ b/apex/permission/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.permission",
     visibility: [
         "//system/apex/tests",
diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
index 6fb7ef4..900193a 100644
--- a/apex/sdkextensions/derive_sdk/derive_sdk.cpp
+++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
@@ -69,7 +69,7 @@
     auto itr = std::min_element(versions.begin(), versions.end());
     std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr);
 
-    if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) {
+    if (!android::base::SetProperty("build.version.extensions.r", prop_value)) {
         LOG(ERROR) << "failed to set sdk_info prop";
         return EXIT_FAILURE;
     }
diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
index a8a7eff..103b53e 100644
--- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
+++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
@@ -38,7 +38,7 @@
 
     private static final int R_EXTENSION_INT;
     static {
-        R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0);
+        R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0);
     }
 
     /**
diff --git a/apex/sdkextensions/framework/java/android/os/ext/test/Test.java b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java
new file mode 100644
index 0000000..1715f49
--- /dev/null
+++ b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java
@@ -0,0 +1,53 @@
+/*
+ * 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.os.ext.test;
+
+import android.annotation.SystemApi;
+
+/**
+ * This class exists temporarily to verify SDK updates are working properly.
+ * @deprecated Do not use.
+ */
+@Deprecated
+public class Test {
+
+    public Test() { }
+
+    /** @hide */
+    public void testA() {}
+
+    /** @hide */
+    public void testB() {}
+
+    /** @hide */
+    public void testC() {}
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public void testD() {}
+
+    public void testE() {}
+
+    /** @hide */
+    @SystemApi
+    public void testF() {}
+
+    /** @hide */
+    @SystemApi
+    public void testG() {}
+
+}
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.bp b/apex/statsd/aidl/Android.bp
index 6d639fd..db5f439 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -13,35 +13,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-
-// TODO(b/145815909): move StatsDimensionsValue.aidl here
 filegroup {
-    name: "statsd_aidl",
+    name: "statsd_java_aidl",
+    srcs: ["**/*.aidl"],
+}
+
+aidl_interface {
+    name: "statsd-aidl",
     srcs: [
         "android/os/IPendingIntentRef.aidl",
         "android/os/IPullAtomCallback.aidl",
         "android/os/IPullAtomResultReceiver.aidl",
         "android/os/IStatsCompanionService.aidl",
         "android/os/IStatsd.aidl",
+        "android/os/StatsDimensionsValueParcel.aidl",
         "android/util/StatsEventParcel.aidl",
     ],
-}
-
-filegroup {
-    name: "statsd_java_aidl",
-    srcs: ["**/*.aidl"],
-}
-
-// This library is currently unused
-aidl_interface {
-    name: "stats-event-parcel-aidl",
-    srcs: ["android/util/StatsEventParcel.aidl"],
     backend: {
         java: {
-            sdk_version: "28",
+            enabled: false, // the platform uses statsd_java_aidl
         },
         cpp: {
-            enabled: false,
+            enabled: true,
+        },
+        ndk: {
+            enabled: true,
         }
     }
 }
diff --git a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl
index 6b9e467..000a699 100644
--- a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl
+++ b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl
@@ -16,7 +16,7 @@
 
 package android.os;
 
-import android.os.StatsDimensionsValue;
+import android.os.StatsDimensionsValueParcel;
 
 /**
   * Binder interface to hold a PendingIntent for StatsCompanionService.
@@ -42,5 +42,5 @@
       */
      oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
                                          long subscriptionRuleId, in String[] cookies,
-                                         in StatsDimensionsValue dimensionsValue);
-}
\ No newline at end of file
+                                         in StatsDimensionsValueParcel dimensionsValueParcel);
+}
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index a2564212..10b1e5b 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -222,18 +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,
-         in long packageVersionCode, in int rollbackReason, in String failingPackageName);
-
-    /**
      * Returns the most recently registered experiment IDs.
      */
     long[] getRegisteredExperimentIds();
diff --git a/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl
new file mode 100644
index 0000000..a8685e3
--- /dev/null
+++ b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl
@@ -0,0 +1,21 @@
+package android.os;
+
+/**
+ * @hide
+ */
+parcelable StatsDimensionsValueParcel {
+    /**
+     * Field equals:
+     *      - atomTag for top level StatsDimensionsValueParcel
+     *      - position in dimension for all other levels
+     */
+    int field;
+    int valueType;
+
+    String stringValue;
+    int intValue;
+    long longValue;
+    boolean boolValue;
+    float floatValue;
+    StatsDimensionsValueParcel[] tupleValue;
+}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index d85ae69..80def47 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,12 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+genrule {
+    name: "statslog-statsd-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" +
+         " --javaPackage com.android.internal.util --javaClass StatsdStatsLog",
+    out: ["com/android/internal/util/StatsdStatsLog.java"],
+}
+
+java_library_static {
+    name: "statslog-statsd",
+    srcs: [
+        ":statslog-statsd-java-gen",
+    ],
+}
+
 filegroup {
     name: "framework-statsd-sources",
     srcs: [
-    "java/**/*.java",
+        "java/**/*.java",
+        ":statsd_java_aidl",
+        ":statslog-statsd-java-gen",
     ],
-    path: "java",
 }
 
 java_library {
@@ -42,8 +58,7 @@
     hostdex: true, // for hiddenapi check
     visibility: [
         "//frameworks/base/apex/statsd:__subpackages__",
-        //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
-        "//frameworks/base",
+        //TODO(b/146167933) remove this
         "//frameworks/opt/net/wifi/service",
     ],
     apex_available: [
@@ -103,7 +118,6 @@
     sdk_version: "core_platform",
 }
 
-// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp
 java_library {
     name: "framework-statsd-stubs-systemapi",
     srcs: [ ":framework-statsd-stubs-srcs-systemapi" ],
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index a1de330..526d17f 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -32,7 +32,7 @@
 import android.os.RemoteException;
 import android.os.StatsFrameworkInitializer;
 import android.util.AndroidException;
-import android.util.Slog;
+import android.util.Log;
 import android.util.StatsEvent;
 import android.util.StatsEventParcel;
 
@@ -155,7 +155,7 @@
                 // can throw IllegalArgumentException
                 service.addConfiguration(configKey, config, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when adding configuration");
+                Log.e(TAG, "Failed to connect to statsmanager when adding configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -191,7 +191,7 @@
                 IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.removeConfiguration(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when removing configuration");
+                Log.e(TAG, "Failed to connect to statsmanager when removing configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -258,7 +258,7 @@
                             mContext.getOpPackageName());
                 }
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
+                Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
                         e);
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
@@ -311,7 +311,7 @@
                 }
 
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when registering data listener.");
+                Log.e(TAG, "Failed to connect to statsmanager when registering data listener.");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -348,7 +348,7 @@
                 }
 
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager "
+                Log.e(TAG, "Failed to connect to statsmanager "
                         + "when registering active configs listener.");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
@@ -387,7 +387,7 @@
                 IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getData(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when getting data");
+                Log.e(TAG, "Failed to connect to statsmanager when getting data");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -424,7 +424,7 @@
                 IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getMetadata(mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsmanager when getting metadata");
+                Log.e(TAG, "Failed to connect to statsmanager when getting metadata");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -464,7 +464,7 @@
                 return service.getRegisteredExperimentIds();
             } catch (RemoteException e) {
                 if (DEBUG) {
-                    Slog.d(TAG,
+                    Log.d(TAG,
                             "Failed to connect to StatsManagerService when getting "
                                     + "registered experiment IDs");
                 }
@@ -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 {
@@ -553,7 +555,7 @@
                     try {
                         resultReceiver.pullFinished(atomTag, success, parcels);
                     } catch (RemoteException e) {
-                        Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
+                        Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
                     }
                 });
             } finally {
diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
index 886130f..35273da 100644
--- a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
+++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
@@ -16,7 +16,7 @@
 package android.os;
 
 import android.annotation.SystemApi;
-import android.util.Slog;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -96,6 +96,47 @@
     }
 
     /**
+     * Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel
+     * TODO(b/149103391): Make StatsDimensionsValue a wrapper on top of
+     * StatsDimensionsValueParcel.
+     *
+     * @hide
+     */
+    public StatsDimensionsValue(StatsDimensionsValueParcel parcel) {
+        mField = parcel.field;
+        mValueType = parcel.valueType;
+        switch (mValueType) {
+            case STRING_VALUE_TYPE:
+                mValue = parcel.stringValue;
+                break;
+            case INT_VALUE_TYPE:
+                mValue = parcel.intValue;
+                break;
+            case LONG_VALUE_TYPE:
+                mValue = parcel.longValue;
+                break;
+            case BOOLEAN_VALUE_TYPE:
+                mValue = parcel.boolValue;
+                break;
+            case FLOAT_VALUE_TYPE:
+                mValue = parcel.floatValue;
+                break;
+            case TUPLE_VALUE_TYPE:
+                StatsDimensionsValue[] values = new StatsDimensionsValue[parcel.tupleValue.length];
+                for (int i = 0; i < parcel.tupleValue.length; i++) {
+                    values[i] = new StatsDimensionsValue(parcel.tupleValue[i]);
+                }
+                mValue = values;
+                break;
+            default:
+                Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType);
+                mValue = null;
+                break;
+        }
+    }
+
+
+    /**
      * Return the field, i.e. the tag of a statsd atom.
      *
      * @return the field
@@ -114,7 +155,7 @@
         try {
             if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return null;
     }
@@ -128,7 +169,7 @@
         try {
             if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return 0;
     }
@@ -142,7 +183,7 @@
         try {
             if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return 0;
     }
@@ -157,7 +198,7 @@
         try {
             if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return false;
     }
@@ -171,7 +212,7 @@
         try {
             if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return 0;
     }
@@ -197,7 +238,7 @@
             }
             return copy;
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
             return null;
         }
     }
@@ -256,7 +297,7 @@
             }
             return sb.toString();
         } catch (ClassCastException e) {
-            Slog.w(TAG, "Failed to successfully get value", e);
+            Log.w(TAG, "Failed to successfully get value", e);
         }
         return "";
     }
@@ -316,11 +357,11 @@
                     return true;
                 }
                 default:
-                    Slog.w(TAG, "readValue of an impossible type " + valueType);
+                    Log.w(TAG, "readValue of an impossible type " + valueType);
                     return false;
             }
         } catch (ClassCastException e) {
-            Slog.w(TAG, "writeValue cast failed", e);
+            Log.w(TAG, "writeValue cast failed", e);
             return false;
         }
     }
@@ -347,7 +388,7 @@
                 return values;
             }
             default:
-                Slog.w(TAG, "readValue of an impossible type " + valueType);
+                Log.w(TAG, "readValue of an impossible type " + valueType);
                 return null;
         }
     }
diff --git a/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java b/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java
index 3d95533..8dc9123 100644
--- a/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java
+++ b/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
 import android.app.StatsManager;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
@@ -24,10 +25,9 @@
 /**
  * Class for performing registration for all stats services
  *
- * TODO(b/148225705) Change to @SystemApi(client=MODULE_LIBRARIES) when the build system is ready.
  * @hide
  */
-@SystemApi
+@SystemApi(client = Client.MODULE_LIBRARIES)
 public class StatsFrameworkInitializer {
     private StatsFrameworkInitializer() {
     }
diff --git a/core/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
similarity index 66%
rename from core/java/android/util/StatsLog.java
rename to apex/statsd/framework/java/android/util/StatsLog.java
index feeff6c..511bc01 100644
--- a/core/java/android/util/StatsLog.java
+++ b/apex/statsd/framework/java/android/util/StatsLog.java
@@ -26,9 +26,10 @@
 import android.content.Context;
 import android.os.IStatsd;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.StatsFrameworkInitializer;
+import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.StatsdStatsLog;
 
 /**
  * StatsLog provides an API for developers to send events to statsd. The events can be used to
@@ -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;
 
@@ -57,17 +59,17 @@
                 IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging start");
+                        Log.d(TAG, "Failed to find statsd when logging start");
                     }
                     return false;
                 }
                 service.sendAppBreadcrumbAtom(label,
-                        FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+                        StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
                 return true;
             } catch (RemoteException e) {
                 sService = null;
                 if (DEBUG) {
-                    Slog.d(TAG, "Failed to connect to statsd when logging start");
+                    Log.d(TAG, "Failed to connect to statsd when logging start");
                 }
                 return false;
             }
@@ -86,17 +88,17 @@
                 IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging stop");
+                        Log.d(TAG, "Failed to find statsd when logging stop");
                     }
                     return false;
                 }
                 service.sendAppBreadcrumbAtom(
-                        label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+                        label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
                 return true;
             } catch (RemoteException e) {
                 sService = null;
                 if (DEBUG) {
-                    Slog.d(TAG, "Failed to connect to statsd when logging stop");
+                    Log.d(TAG, "Failed to connect to statsd when logging stop");
                 }
                 return false;
             }
@@ -115,17 +117,17 @@
                 IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging event");
+                        Log.d(TAG, "Failed to find statsd when logging event");
                     }
                     return false;
                 }
                 service.sendAppBreadcrumbAtom(
-                        label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+                        label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
                 return true;
             } catch (RemoteException e) {
                 sService = null;
                 if (DEBUG) {
-                    Slog.d(TAG, "Failed to connect to statsd when logging event");
+                    Log.d(TAG, "Failed to connect to statsd when logging event");
                 }
                 return false;
             }
@@ -152,77 +154,36 @@
     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);
         }
+        StatsdStatsLog.write(StatsdStatsLog.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,
+                false);
+        return true;
     }
 
-    /**
-     * Logs an event for watchdog rollbacks.
-     *
-     * @param rollbackType          state of the rollback.
-     * @param packageName           package name being rolled back.
-     * @param packageVersionCode    version of the package being rolled back.
-     * @param rollbackReason        reason the package is being rolled back.
-     * @param failingPackageName    the package name causing the failure.
-     *
-     * @return True if the log request was sent to statsd.
-     *
-     * @hide
-     */
-    @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
-    public static boolean logWatchdogRollbackOccurred(int rollbackType, String packageName,
-            long packageVersionCode, int rollbackReason, String failingPackageName) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging event");
-                    }
-                    return false;
-                }
-
-                service.sendWatchdogRollbackOccurredAtom(rollbackType, packageName,
-                        packageVersionCode, rollbackReason, failingPackageName);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Slog.d(TAG,
-                            "Failed to connect to StatsCompanionService when logging "
-                                    + "WatchdogRollbackOccurred");
-                }
-                return false;
-            }
-        }
-    }
-
-
     private static IStatsd getIStatsdLocked() throws RemoteException {
         if (sService != null) {
             return sService;
         }
-        sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
+        sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer
+            .getStatsServiceManager()
+            .getStatsdServiceRegisterer()
+            .get());
         return sService;
     }
 
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index 9103848..0f8a151 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -1,17 +1,30 @@
 // Statsd Service jar, which will eventually be put in the statsd mainline apex.
 // service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS.
 // This jar will contain StatsCompanionService
+
+filegroup {
+    name: "service-statsd-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+}
+
 java_library {
     name: "service-statsd",
     installable: true,
 
     srcs: [
-        "java/**/*.java",
+        ":service-statsd-sources",
     ],
-    // TODO: link against the proper stubs (b/146084685).
+    // TODO(b/146209659): Use system_current instead once framework-statsd compiles against
+    // system_current.
+    sdk_version: "core_platform",
     libs: [
-        "framework-minus-apex",
-        "services.core",
+        "framework-annotations-lib",
+        "framework-statsd",
+        // TODO(b/146758669): Remove this line after nullability annotations are system APIs.
+        "android_system_stubs_current",
+        "services-stubs",
     ],
     apex_available: [
         "com.android.os.statsd",
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..c1ba73f 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -24,7 +24,8 @@
 import android.os.IPendingIntentRef;
 import android.os.Process;
 import android.os.StatsDimensionsValue;
-import android.util.Slog;
+import android.os.StatsDimensionsValueParcel;
+import android.util.Log;
 
 import com.android.server.SystemService;
 
@@ -38,11 +39,18 @@
     private static final String TAG = "StatsCompanion";
     private static final boolean DEBUG = false;
 
-    static void enforceStatsCompanionPermission(Context context) {
+    private static final int AID_STATSD = 1066;
+
+    private static final String STATS_COMPANION_SERVICE = "statscompanion";
+    private static final String STATS_MANAGER_SERVICE = "statsmanager";
+
+    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");
+        }
     }
 
     /**
@@ -64,14 +72,12 @@
             mStatsManagerService.setStatsCompanionService(mStatsCompanionService);
 
             try {
-                publishBinderService(Context.STATS_COMPANION_SERVICE,
-                        mStatsCompanionService);
-                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
-                publishBinderService(Context.STATS_MANAGER_SERVICE,
-                        mStatsManagerService);
-                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE);
+                publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService);
+                if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE);
+                publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService);
+                if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE);
             } catch (Exception e) {
-                Slog.e(TAG, "Failed to publishBinderService", e);
+                Log.e(TAG, "Failed to publishBinderService", e);
             }
         }
 
@@ -114,35 +120,37 @@
 
         @Override
         public void sendDataBroadcast(long lastReportTimeNs) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
             try {
                 mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null);
             } catch (PendingIntent.CanceledException e) {
-                Slog.w(TAG, "Unable to send PendingIntent");
+                Log.w(TAG, "Unable to send PendingIntent");
             }
         }
 
         @Override
         public void sendActiveConfigsChangedBroadcast(long[] configIds) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
             try {
                 mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null);
                 if (DEBUG) {
-                    Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
+                    Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
                 }
             } catch (PendingIntent.CanceledException e) {
-                Slog.w(TAG, "Unable to send active configs changed broadcast using PendingIntent");
+                Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent");
             }
         }
 
         @Override
         public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
-                long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) {
-            enforceStatsCompanionPermission(mContext);
+                long subscriptionRuleId, String[] cookies,
+                StatsDimensionsValueParcel dimensionsValueParcel) {
+            enforceStatsdCallingUid();
+            StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel);
             Intent intent =
                     new Intent()
                             .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
@@ -158,7 +166,7 @@
                     StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList);
 
             if (DEBUG) {
-                Slog.d(TAG,
+                Log.d(TAG,
                         String.format(
                                 "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}",
                                 configUid, configId, subscriptionId, subscriptionRuleId,
@@ -168,7 +176,7 @@
             try {
                 mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
             } catch (PendingIntent.CanceledException e) {
-                Slog.w(TAG,
+                Log.w(TAG,
                         "Unable to send using PendingIntent from uid " + configUid
                                 + "; presumably it had been cancelled.");
             }
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..cb167c3 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -40,14 +40,10 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.Slog;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.LooperStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.BinderCallsStatsService;
-import com.android.server.LocalServices;
 
 import libcore.io.IoUtils;
 
@@ -89,6 +85,12 @@
 
     public static final int DEATH_THRESHOLD = 10;
 
+    // TODO(b/149090705): Implement an alternative to sending broadcast with @hide flag
+    // FLAG_RECEIVER_INCLUDE_BACKGROUND. Instead of using the flag, find the
+    // list of registered broadcast receivers and send them directed broadcasts
+    // to wake them up. See b/147374337.
+    private static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
+
     static final class CompanionHandler extends Handler {
         CompanionHandler(Looper looper) {
             super(looper);
@@ -126,7 +128,7 @@
             public void onReceive(Context context, Intent intent) {
                 synchronized (sStatsdLock) {
                     if (sStatsd == null) {
-                        Slog.w(TAG, "Could not access statsd for UserUpdateReceiver");
+                        Log.w(TAG, "Could not access statsd for UserUpdateReceiver");
                         return;
                     }
                     try {
@@ -134,14 +136,14 @@
                         // Needed since the new user basically has a version of every app.
                         informAllUidsLocked(context);
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
+                        Log.e(TAG, "Failed to inform statsd latest update of all apps", e);
                         forgetEverythingLocked();
                     }
                 }
             }
         };
         mShutdownEventReceiver = new ShutdownEventReceiver();
-        if (DEBUG) Slog.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED.");
+        if (DEBUG) Log.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED.");
         HandlerThread handlerThread = new HandlerThread(TAG);
         handlerThread.start();
         mHandler = new CompanionHandler(handlerThread.getLooper());
@@ -171,21 +173,21 @@
         PackageManager pm = context.getPackageManager();
         final List<UserHandle> users = um.getUserHandles(true);
         if (DEBUG) {
-            Slog.d(TAG, "Iterating over " + users.size() + " userHandles.");
+            Log.d(TAG, "Iterating over " + users.size() + " userHandles.");
         }
 
         ParcelFileDescriptor[] fds;
         try {
             fds = ParcelFileDescriptor.createPipe();
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to create a pipe to send uid map data.", e);
+            Log.e(TAG, "Failed to create a pipe to send uid map data.", e);
             return;
         }
         sStatsd.informAllUidData(fds[0]);
         try {
             fds[0].close();
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to close the read side of the pipe.", e);
+            Log.e(TAG, "Failed to close the read side of the pipe.", e);
         }
         final ParcelFileDescriptor writeFd = fds[1];
         HandlerThread backgroundThread = new HandlerThread(
@@ -239,7 +241,7 @@
                 }
                 output.flush();
                 if (DEBUG) {
-                    Slog.d(TAG, "Sent data for " + numRecords + " apps");
+                    Log.d(TAG, "Sent data for " + numRecords + " apps");
                 }
             } finally {
                 IoUtils.closeQuietly(fout);
@@ -261,10 +263,10 @@
                     && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                 return; // Keep only replacing or normal add and remove.
             }
-            if (DEBUG) Slog.d(TAG, "StatsCompanionService noticed an app was updated.");
+            if (DEBUG) Log.d(TAG, "StatsCompanionService noticed an app was updated.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of an app update");
+                    Log.w(TAG, "Could not access statsd to inform it of an app update");
                     return;
                 }
                 try {
@@ -299,7 +301,7 @@
                                 installer == null ? "" : installer);
                     }
                 } catch (Exception e) {
-                    Slog.w(TAG, "Failed to inform statsd of an app update", e);
+                    Log.w(TAG, "Failed to inform statsd of an app update", e);
                 }
             }
         }
@@ -308,18 +310,18 @@
     public final static class AnomalyAlarmListener implements OnAlarmListener {
         @Override
         public void onAlarm() {
-            Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred at time "
+            Log.i(TAG, "StatsCompanionService believes an anomaly has occurred at time "
                     + System.currentTimeMillis() + "ms.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    Log.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
                     return;
                 }
                 try {
                     // Two-way call to statsd to retain AlarmManager wakelock
                     sStatsd.informAnomalyAlarmFired();
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
+                    Log.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
                 }
             }
             // AlarmManager releases its own wakelock here.
@@ -330,18 +332,18 @@
         @Override
         public void onAlarm() {
             if (DEBUG) {
-                Slog.d(TAG, "Time to poll something.");
+                Log.d(TAG, "Time to poll something.");
             }
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
+                    Log.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
                     return;
                 }
                 try {
                     // Two-way call to statsd to retain AlarmManager wakelock
                     sStatsd.informPollAlarmFired();
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
+                    Log.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
                 }
             }
         }
@@ -351,18 +353,18 @@
         @Override
         public void onAlarm() {
             if (DEBUG) {
-                Slog.d(TAG, "Time to trigger periodic alarm.");
+                Log.d(TAG, "Time to trigger periodic alarm.");
             }
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of periodic alarm firing.");
+                    Log.w(TAG, "Could not access statsd to inform it of periodic alarm firing.");
                     return;
                 }
                 try {
                     // Two-way call to statsd to retain AlarmManager wakelock
                     sStatsd.informAlarmForSubscriberTriggeringFired();
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to inform statsd of periodic alarm firing.", e);
+                    Log.w(TAG, "Failed to inform statsd of periodic alarm firing.", e);
                 }
             }
             // AlarmManager releases its own wakelock here.
@@ -381,16 +383,16 @@
                 return;
             }
 
-            Slog.i(TAG, "StatsCompanionService noticed a shutdown.");
+            Log.i(TAG, "StatsCompanionService noticed a shutdown.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of a shutdown event.");
+                    Log.w(TAG, "Could not access statsd to inform it of a shutdown event.");
                     return;
                 }
                 try {
                     sStatsd.informDeviceShutdown();
                 } catch (Exception e) {
-                    Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e);
+                    Log.w(TAG, "Failed to inform statsd of a shutdown event.", e);
                 }
             }
         }
@@ -398,8 +400,8 @@
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
-        if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
+        StatsCompanion.enforceStatsdCallingUid();
+        if (DEBUG) Log.d(TAG, "Setting anomaly alarm for " + timestampMs);
         final long callingToken = Binder.clearCallingIdentity();
         try {
             // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will
@@ -414,8 +416,8 @@
 
     @Override // Binder call
     public void cancelAnomalyAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
-        if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
+        StatsCompanion.enforceStatsdCallingUid();
+        if (DEBUG) Log.d(TAG, "Cancelling anomaly alarm");
         final long callingToken = Binder.clearCallingIdentity();
         try {
             mAlarmManager.cancel(mAnomalyAlarmListener);
@@ -426,9 +428,9 @@
 
     @Override // Binder call
     public void setAlarmForSubscriberTriggering(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
-            Slog.d(TAG,
+            Log.d(TAG,
                     "Setting periodic alarm in about " + (timestampMs
                             - SystemClock.elapsedRealtime()));
         }
@@ -445,9 +447,9 @@
 
     @Override // Binder call
     public void cancelAlarmForSubscriberTriggering() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
-            Slog.d(TAG, "Cancelling periodic alarm");
+            Log.d(TAG, "Cancelling periodic alarm");
         }
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -459,9 +461,9 @@
 
     @Override // Binder call
     public void setPullingAlarm(long nextPullTimeMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
-            Slog.d(TAG, "Setting pulling alarm in about "
+            Log.d(TAG, "Setting pulling alarm in about "
                     + (nextPullTimeMs - SystemClock.elapsedRealtime()));
         }
         final long callingToken = Binder.clearCallingIdentity();
@@ -477,9 +479,9 @@
 
     @Override // Binder call
     public void cancelPullingAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
-            Slog.d(TAG, "Cancelling pulling alarm");
+            Log.d(TAG, "Cancelling pulling alarm");
         }
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -491,25 +493,25 @@
 
     @Override // Binder call
     public void statsdReady() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
-            Slog.d(TAG, "learned that statsdReady");
+            Log.d(TAG, "learned that statsdReady");
         }
         sayHiToStatsd(); // tell statsd that we're ready too and link to it
         mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED)
-                        .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
+                        .addFlags(FLAG_RECEIVER_INCLUDE_BACKGROUND),
                 UserHandle.SYSTEM, android.Manifest.permission.DUMP);
     }
 
     @Override
     public void triggerUidSnapshot() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         synchronized (sStatsdLock) {
             final long token = Binder.clearCallingIdentity();
             try {
                 informAllUidsLocked(mContext);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+                Log.e(TAG, "Failed to trigger uid snapshot.", e);
             } finally {
                 restoreCallingIdentity(token);
             }
@@ -518,7 +520,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;
     }
 
@@ -540,7 +542,7 @@
      * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
      */
     void systemReady() {
-        if (DEBUG) Slog.d(TAG, "Learned that systemReady");
+        if (DEBUG) Log.d(TAG, "Learned that systemReady");
         sayHiToStatsd();
     }
 
@@ -555,27 +557,27 @@
     private void sayHiToStatsd() {
         synchronized (sStatsdLock) {
             if (sStatsd != null) {
-                Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
+                Log.e(TAG, "Trying to fetch statsd, but it was already fetched",
                         new IllegalStateException(
                                 "sStatsd is not null when being fetched"));
                 return;
             }
             sStatsd = fetchStatsdService();
             if (sStatsd == null) {
-                Slog.i(TAG,
+                Log.i(TAG,
                         "Could not yet find statsd to tell it that StatsCompanion is "
                                 + "alive.");
                 return;
             }
             mStatsManagerService.statsdReady(sStatsd);
-            if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
+            if (DEBUG) Log.d(TAG, "Saying hi to statsd");
             try {
                 sStatsd.statsCompanionReady();
                 // If the statsCompanionReady two-way binder call returns, link to statsd.
                 try {
                     sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+                    Log.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
                     forgetEverythingLocked();
                 }
                 // Setup broadcast receiver for updates.
@@ -605,9 +607,9 @@
                 } finally {
                     restoreCallingIdentity(token);
                 }
-                Slog.i(TAG, "Told statsd that StatsCompanionService is alive.");
+                Log.i(TAG, "Told statsd that StatsCompanionService is alive.");
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+                Log.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
                 forgetEverythingLocked();
             }
         }
@@ -616,7 +618,7 @@
     private class StatsdDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
-            Slog.i(TAG, "Statsd is dead - erase all my knowledge, except pullers");
+            Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers");
             synchronized (sStatsdLock) {
                 long now = SystemClock.elapsedRealtime();
                 for (Long timeMillis : mDeathTimeMillis) {
@@ -656,22 +658,15 @@
         cancelAnomalyAlarm();
         cancelPullingAlarm();
 
-        BinderCallsStatsService.Internal binderStats =
-                LocalServices.getService(BinderCallsStatsService.Internal.class);
-        if (binderStats != null) {
-            binderStats.reset();
-        }
-
-        LooperStats looperStats = LocalServices.getService(LooperStats.class);
-        if (looperStats != null) {
-            looperStats.reset();
-        }
         mStatsManagerService.statsdNotReady();
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
 
         synchronized (sStatsdLock) {
             writer.println(
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..4e4bc40 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -30,7 +30,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -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,11 +187,12 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.registerPullAtomCallback(
                     callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+            Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -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,10 +215,11 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.unregisterPullAtomCallback(callingUid, atomTag);
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+            Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -241,7 +243,7 @@
                 statsd.setDataFetchOperation(configId, pir, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to setDataFetchOperation with statsd");
+            Log.e(TAG, "Failed to setDataFetchOperation with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -262,7 +264,7 @@
                 statsd.removeDataFetchOperation(configId, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to removeDataFetchOperation with statsd");
+            Log.e(TAG, "Failed to removeDataFetchOperation with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -285,7 +287,7 @@
                 return statsd.setActiveConfigsChangedOperation(pir, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd");
+            Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -306,7 +308,7 @@
                 statsd.removeActiveConfigsChangedOperation(callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd");
+            Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -334,7 +336,7 @@
                         configId, subscriberId, pir, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to setBroadcastSubscriber with statsd");
+            Log.e(TAG, "Failed to setBroadcastSubscriber with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -360,7 +362,7 @@
                 statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to unsetBroadcastSubscriber with statsd");
+            Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -376,7 +378,7 @@
                 return statsd.getRegisteredExperimentIds();
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
+            Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
             throw new IllegalStateException(e.getMessage(), e);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -394,7 +396,7 @@
                 return statsd.getMetadata();
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to getMetadata with statsd");
+            Log.e(TAG, "Failed to getMetadata with statsd");
             throw new IllegalStateException(e.getMessage(), e);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -413,7 +415,7 @@
                 return statsd.getData(key, callingUid);
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to getData with statsd");
+            Log.e(TAG, "Failed to getData with statsd");
             throw new IllegalStateException(e.getMessage(), e);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -434,7 +436,7 @@
                 return;
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to addConfiguration with statsd");
+            Log.e(TAG, "Failed to addConfiguration with statsd");
             throw new IllegalStateException(e.getMessage(), e);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -455,7 +457,7 @@
                 return;
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to removeConfiguration with statsd");
+            Log.e(TAG, "Failed to removeConfiguration with statsd");
             throw new IllegalStateException(e.getMessage(), e);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -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
      *
@@ -513,7 +522,7 @@
                 try {
                     mLock.wait(STATSD_TIMEOUT_MILLIS);
                 } catch (InterruptedException e) {
-                    Slog.e(TAG, "wait for statsd interrupted");
+                    Log.e(TAG, "wait for statsd interrupted");
                 }
             }
             return mStatsd;
@@ -569,7 +578,7 @@
             registerAllActiveConfigsChangedOperations(statsd);
             registerAllBroadcastSubscribers(statsd);
         } catch (RemoteException e) {
-            Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+            Log.e(TAG, "StatsManager failed to (re-)register data with statsd");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp
index 22e7301..a9cd0cc 100644
--- a/apex/statsd/testing/Android.bp
+++ b/apex/statsd/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.os.statsd",
     visibility: [
         "//system/apex/tests",
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 87a5cd7..826d409 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2873,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
@@ -2888,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
@@ -2945,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);
@@ -3850,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();
@@ -4002,7 +4015,7 @@
     method public android.util.Size getAppTaskThumbnailSize();
     method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
     method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
-    method @Nullable public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
     method public int getLargeMemoryClass();
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
@@ -4542,12 +4555,13 @@
     method public int getPackageUid();
     method public int getPid();
     method @NonNull public String getProcessName();
-    method public int getPss();
+    method public long getPss();
     method public int getRealUid();
     method public int getReason();
-    method public int getRss();
+    method public long getRss();
     method public int getStatus();
     method public long getTimestamp();
+    method @NonNull public android.os.UserHandle getUserHandle();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR;
     field public static final int REASON_ANR = 6; // 0x6
@@ -6803,7 +6817,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 {
@@ -26799,7 +26813,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";
   }
@@ -28772,6 +28785,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";
@@ -28797,6 +28811,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";
@@ -28890,6 +28905,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";
@@ -28937,6 +28953,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";
@@ -28953,6 +28971,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";
@@ -29018,6 +29037,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";
@@ -29078,6 +29098,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";
@@ -29682,7 +29703,7 @@
     method public long getReportTimestamp();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
-    field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttemped";
+    field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted";
     field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded";
     field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult";
     field public static final int NETWORK_PROBE_DNS = 4; // 0x4
@@ -30119,7 +30140,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;
@@ -30759,7 +30780,6 @@
     method public void close();
     method public void continueCall(int) throws android.net.sip.SipException;
     method public void endCall() throws android.net.sip.SipException;
-    method @Nullable public android.net.rtp.AudioGroup getAudioGroup();
     method public android.net.sip.SipProfile getLocalProfile();
     method public android.net.sip.SipProfile getPeerProfile();
     method public int getState();
@@ -30770,7 +30790,6 @@
     method public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException;
     method public void sendDtmf(int);
     method public void sendDtmf(int, android.os.Message);
-    method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup);
     method public void setListener(android.net.sip.SipAudioCall.Listener);
     method public void setListener(android.net.sip.SipAudioCall.Listener, boolean);
     method public void setSpeakerMode(boolean);
@@ -30819,7 +30838,6 @@
     method public void close(String) throws android.net.sip.SipException;
     method public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException;
     method public static String getCallId(android.content.Intent);
-    method @NonNull public java.util.List<android.net.sip.SipProfile> getListOfProfiles() throws android.net.sip.SipException;
     method public static String getOfferSessionDescription(android.content.Intent);
     method public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException;
     method public static boolean isApiSupported(android.content.Context);
@@ -30837,11 +30855,6 @@
     method public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException;
     method public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException;
     method public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException;
-    field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED";
-    field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL";
-    field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE";
-    field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP";
-    field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP";
     field public static final String EXTRA_CALL_ID = "android:sipCallID";
     field public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
     field public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65
@@ -30851,7 +30864,6 @@
     method public int describeContents();
     method public String getAuthUserName();
     method public boolean getAutoRegistration();
-    method public int getCallingUid();
     method public String getDisplayName();
     method public String getPassword();
     method public int getPort();
@@ -31275,6 +31287,7 @@
     method public boolean isP2pSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method @Deprecated public boolean isScanAlwaysAvailable();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isScanThrottleEnabled();
     method public boolean isStaApConcurrencySupported();
     method public boolean isTdlsSupported();
     method public boolean isWapiSupported();
@@ -31747,7 +31760,7 @@
     field public static final int GROUP_OWNER_INTENT_MAX = 15; // 0xf
     field public static final int GROUP_OWNER_INTENT_MIN = 0; // 0x0
     field public String deviceAddress;
-    field public int groupOwnerIntent;
+    field @IntRange(from=0, to=15) public int groupOwnerIntent;
     field public android.net.wifi.WpsInfo wps;
   }
 
@@ -31939,14 +31952,14 @@
     method public int getDeviceType();
     method public int getMaxThroughput();
     method public boolean isContentProtectionSupported();
+    method public boolean isEnabled();
     method public boolean isSessionAvailable();
-    method public boolean isWfdEnabled();
     method public void setContentProtectionSupported(boolean);
-    method public void setControlPort(int);
+    method public void setControlPort(@IntRange(from=0) int);
     method public boolean setDeviceType(int);
-    method public void setMaxThroughput(int);
+    method public void setEnabled(boolean);
+    method public void setMaxThroughput(@IntRange(from=0) int);
     method public void setSessionAvailable(boolean);
-    method public void setWfdEnabled(boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pWfdInfo> CREATOR;
     field public static final int DEVICE_TYPE_PRIMARY_SINK = 1; // 0x1
@@ -35680,6 +35693,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;
@@ -37024,6 +37038,15 @@
 
 }
 
+package android.os.ext.test {
+
+  @Deprecated public class Test {
+    ctor @Deprecated public Test();
+    method @Deprecated public void testE();
+  }
+
+}
+
 package android.os.health {
 
   public class HealthStats {
@@ -40394,6 +40417,7 @@
     field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
     field public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED = "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED";
     field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
+    field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
@@ -43973,7 +43997,7 @@
     field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
     field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
     field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
-    field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE";
+    field public static final String META_DATA_TOGGLEABLE_TILE = "android.service.quicksettings.TOGGLEABLE_TILE";
   }
 
 }
@@ -44054,8 +44078,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 {
@@ -47290,7 +47314,7 @@
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
     method public void onUserMobileDataStateChanged(boolean);
     field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
     field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
     field public static final int LISTEN_CALL_STATE = 32; // 0x20
@@ -47303,7 +47327,7 @@
     field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
     field public static final int LISTEN_NONE = 0; // 0x0
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
     field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
     field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
@@ -60813,7 +60837,6 @@
     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
   }
@@ -60868,7 +60891,7 @@
     method public int getGravity();
     method public float getHorizontalMargin();
     method public float getVerticalMargin();
-    method @Deprecated public android.view.View getView();
+    method @Deprecated @Nullable public android.view.View getView();
     method public int getXOffset();
     method public int getYOffset();
     method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 90531b1..e5a1f28 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -1,4 +1,157 @@
 // Signature format: 2.0
+package android.net {
+
+  public final class TetheredClient implements android.os.Parcelable {
+    ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses();
+    method @NonNull public android.net.MacAddress getMacAddress();
+    method public int getTetheringType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR;
+  }
+
+  public static final class TetheredClient.AddressInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.LinkAddress getAddress();
+    method @Nullable public String getHostname();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
+  }
+
+  public class TetheringConstants {
+    ctor public TetheringConstants();
+    field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+    field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    field public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    field public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+  }
+
+  public class TetheringManager {
+    ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>);
+    method public int getLastTetherError(@NonNull String);
+    method @NonNull public String[] getTetherableBluetoothRegexs();
+    method @NonNull public String[] getTetherableIfaces();
+    method @NonNull public String[] getTetherableUsbRegexs();
+    method @NonNull public String[] getTetherableWifiRegexs();
+    method @NonNull public String[] getTetheredIfaces();
+    method @NonNull public String[] getTetheringErroredIfaces();
+    method public boolean isTetheringSupported();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
+    method @Deprecated public int setUsbTethering(boolean);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+    method @Deprecated public int tether(@NonNull String);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @Deprecated public int untether(@NonNull String);
+    field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+    field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_ETHERNET = 5; // 0x5
+    field public static final int TETHERING_INVALID = -1; // 0xffffffff
+    field public static final int TETHERING_NCM = 4; // 0x4
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+    field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+    field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9
+    field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8
+    field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+    field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+    field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5
+    field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+    field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+    field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+    field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+    field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+    field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+    field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+  }
+
+  public static interface TetheringManager.OnTetheringEntitlementResultListener {
+    method public void onTetheringEntitlementResult(int);
+  }
+
+  public abstract static class TetheringManager.StartTetheringCallback {
+    ctor public TetheringManager.StartTetheringCallback();
+    method public void onTetheringFailed(int);
+    method public void onTetheringStarted();
+  }
+
+  public abstract static class TetheringManager.TetheringEventCallback {
+    ctor public TetheringManager.TetheringEventCallback();
+    method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
+    method public void onError(@NonNull String, int);
+    method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+    method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheringSupported(boolean);
+    method public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]);
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+  }
+
+  public static class TetheringManager.TetheringRequest {
+  }
+
+  public static class TetheringManager.TetheringRequest.Builder {
+    ctor public TetheringManager.TetheringRequest.Builder(int);
+    method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+  }
+
+}
+
+package android.os {
+
+  public class StatsFrameworkInitializer {
+    method public static void registerServiceWrappers();
+    method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager);
+  }
+
+  public class StatsServiceManager {
+    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer();
+    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer();
+    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsdServiceRegisterer();
+  }
+
+  public static class StatsServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor public StatsServiceManager.ServiceNotFoundException(@NonNull String);
+  }
+
+  public static final class StatsServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder get();
+    method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException;
+  }
+
+}
+
+package android.os.ext.test {
+
+  @Deprecated public class Test {
+    method @Deprecated public void testD();
+  }
+
+}
+
 package android.util {
 
   public final class Log {
diff --git a/api/system-current.txt b/api/system-current.txt
index 1128556..68ec02d 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";
@@ -584,10 +585,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
   }
 
-  public final class ApplicationExitInfo implements android.os.Parcelable {
-    method @NonNull public android.os.UserHandle getUserHandle();
-  }
-
   public class BroadcastOptions {
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND") public void setBackgroundActivityStartsAllowed(boolean);
@@ -692,7 +689,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;
@@ -700,7 +697,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";
@@ -1151,6 +1148,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 {
@@ -1472,9 +1479,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();
@@ -2098,6 +2105,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
@@ -2197,10 +2208,13 @@
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+    field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     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
@@ -2284,7 +2298,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 {
@@ -3678,17 +3692,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;
@@ -3848,6 +3862,20 @@
     method @NonNull public android.location.GnssReflectingPlane.Builder setLongitudeDegrees(@FloatRange(from=-180.0F, to=180.0f) double);
   }
 
+  public final class GnssRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isFullTracking();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssRequest> CREATOR;
+  }
+
+  public static final class GnssRequest.Builder {
+    ctor public GnssRequest.Builder();
+    ctor public GnssRequest.Builder(@NonNull android.location.GnssRequest);
+    method @NonNull public android.location.GnssRequest build();
+    method @NonNull public android.location.GnssRequest.Builder setFullTracking(boolean);
+  }
+
   public final class GnssSingleSatCorrection implements android.os.Parcelable {
     method public int describeContents();
     method @FloatRange(from=0.0f, fromInclusive=false) public float getCarrierFrequencyHz();
@@ -4121,6 +4149,7 @@
     method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.LOCATION_HARDWARE}) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
@@ -4198,15 +4227,15 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
   }
 
-  public final class AudioDeviceAddress implements android.os.Parcelable {
-    ctor public AudioDeviceAddress(@NonNull android.media.AudioDeviceInfo);
-    ctor public AudioDeviceAddress(int, int, @NonNull String);
+  public final class AudioDevice implements android.os.Parcelable {
+    ctor public AudioDevice(@NonNull android.media.AudioDeviceInfo);
+    ctor public AudioDevice(int, int, @NonNull String);
     method public int describeContents();
     method @NonNull public String getAddress();
     method public int getRole();
     method public int getType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDeviceAddress> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDevice> CREATOR;
     field public static final int ROLE_INPUT = 1; // 0x1
     field public static final int ROLE_OUTPUT = 2; // 0x2
   }
@@ -4243,11 +4272,11 @@
     method @IntRange(from=0) public int getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDevice> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) public int getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDevice getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
@@ -4261,7 +4290,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDevice);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4317,7 +4346,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 {
@@ -4357,7 +4386,7 @@
 package android.media.audiofx {
 
   public class AudioEffect {
-    ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDeviceAddress);
+    ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDevice);
   }
 
 }
@@ -5728,7 +5757,6 @@
     method public int getFreqOffset();
     method public int getHierarchy();
     method @NonNull public boolean[] getLayerErrors();
-    method public int getLberCn();
     method public int getLnbVoltage();
     method public int getMer();
     method public int getModulation();
@@ -5740,37 +5768,32 @@
     method public int getSnr();
     method public int getSpectralInversion();
     method public int getSymbolRate();
-    method public int getVberCn();
-    method public int getXerCn();
     method public boolean isDemodLocked();
     method public boolean isEwbs();
     method public boolean isLnaOn();
     method public boolean isRfLock();
     field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe
-    field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 24; // 0x18
+    field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15
     field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2
     field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0
     field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd
     field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8
-    field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 21; // 0x15
-    field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 22; // 0x16
+    field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 18; // 0x12
+    field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 19; // 0x13
     field public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = 16; // 0x10
-    field public static final int FRONTEND_STATUS_TYPE_LBER_CN = 18; // 0x12
     field public static final int FRONTEND_STATUS_TYPE_LNA = 15; // 0xf
     field public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = 11; // 0xb
-    field public static final int FRONTEND_STATUS_TYPE_MER = 20; // 0x14
+    field public static final int FRONTEND_STATUS_TYPE_MER = 17; // 0x11
     field public static final int FRONTEND_STATUS_TYPE_MODULATION = 9; // 0x9
     field public static final int FRONTEND_STATUS_TYPE_PER = 3; // 0x3
     field public static final int FRONTEND_STATUS_TYPE_PLP_ID = 12; // 0xc
     field public static final int FRONTEND_STATUS_TYPE_PRE_BER = 4; // 0x4
-    field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 23; // 0x17
+    field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 20; // 0x14
     field public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = 5; // 0x5
     field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
     field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
     field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
     field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
-    field public static final int FRONTEND_STATUS_TYPE_VBER_CN = 17; // 0x11
-    field public static final int FRONTEND_STATUS_TYPE_XER_CN = 19; // 0x13
   }
 
   public static class FrontendStatus.Atsc3PlpInfo {
@@ -6290,7 +6313,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
@@ -6342,6 +6367,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);
   }
 
@@ -6426,7 +6453,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);
   }
@@ -7189,6 +7215,28 @@
 
 }
 
+package android.net.sip {
+
+  public class SipAudioCall {
+    method @Nullable public android.net.rtp.AudioGroup getAudioGroup();
+    method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup);
+  }
+
+  public class SipManager {
+    method @NonNull public java.util.List<android.net.sip.SipProfile> getProfiles() throws android.net.sip.SipException;
+    field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED";
+    field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL";
+    field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE";
+    field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP";
+    field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP";
+  }
+
+  public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable {
+    method public int getCallingUid();
+  }
+
+}
+
 package android.net.util {
 
   public final class SocketUtils {
@@ -7517,9 +7565,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
@@ -7529,7 +7574,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;
@@ -7558,13 +7602,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
@@ -7575,7 +7618,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
@@ -7678,6 +7720,7 @@
     method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setScanThrottleEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setVerboseLoggingEnabled(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -7822,23 +7865,23 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
   }
 
-  public final class WifiOemConfigStoreMigrationHook {
-    method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+  public final class WifiOemMigrationHook {
+    method @Nullable public static android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData loadFromConfigStore();
   }
 
-  public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+  public static final class WifiOemMigrationHook.ConfigStoreMigrationData implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
     method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData> CREATOR;
   }
 
-  public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
-    ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
-    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
-    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
-    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+  public static final class WifiOemMigrationHook.ConfigStoreMigrationData.Builder {
+    ctor public WifiOemMigrationHook.ConfigStoreMigrationData.Builder();
+    method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData build();
+    method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+    method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
   }
 
   public class WifiScanner {
@@ -8096,7 +8139,7 @@
 
   public final class WifiP2pGroupList implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public java.util.Collection<android.net.wifi.p2p.WifiP2pGroup> getGroupList();
+    method @NonNull public java.util.List<android.net.wifi.p2p.WifiP2pGroup> getGroupList();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pGroupList> CREATOR;
   }
@@ -8104,12 +8147,13 @@
   public class WifiP2pManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void deletePersistentGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, int, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void factoryReset(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void listen(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, boolean, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public void requestPersistentGroupInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void setDeviceName(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull String, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method @RequiresPermission(allOf={android.Manifest.permission.CONNECTIVITY_INTERNAL, android.Manifest.permission.CONFIGURE_WIFI_DISPLAY}) public void setMiracastMode(int);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) public void setWfdInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pWfdInfo, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void setWifiP2pChannels(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, int, int, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void startListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void stopListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     field public static final String ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED";
     field public static final int MIRACAST_DISABLED = 0; // 0x0
     field public static final int MIRACAST_SINK = 2; // 0x2
@@ -8199,7 +8243,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();
@@ -8235,12 +8279,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);
@@ -8265,10 +8309,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);
@@ -8830,26 +8874,6 @@
     field public static final int TUPLE_VALUE_TYPE = 7; // 0x7
   }
 
-  public class StatsFrameworkInitializer {
-    method public static void registerServiceWrappers();
-    method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager);
-  }
-
-  public class StatsServiceManager {
-    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer();
-    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer();
-    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsdServiceRegisterer();
-  }
-
-  public static class StatsServiceManager.ServiceNotFoundException extends java.lang.Exception {
-    ctor public StatsServiceManager.ServiceNotFoundException(@NonNull String);
-  }
-
-  public static final class StatsServiceManager.ServiceRegisterer {
-    method @Nullable public android.os.IBinder get();
-    method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException;
-  }
-
   public class SystemConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
@@ -9129,6 +9153,15 @@
 
 }
 
+package android.os.ext.test {
+
+  @Deprecated public class Test {
+    method @Deprecated public void testF();
+    method @Deprecated public void testG();
+  }
+
+}
+
 package android.os.image {
 
   public class DynamicSystemClient {
@@ -12409,7 +12442,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";
@@ -14115,6 +14147,10 @@
     method public boolean isContentCaptureFeatureEnabled();
   }
 
+  public abstract class ContentCaptureSession implements java.lang.AutoCloseable {
+    field public static final int NO_SESSION_ID = 0; // 0x0
+  }
+
   public final class ViewNode extends android.app.assist.AssistStructure.ViewNode {
     method @Nullable public android.view.autofill.AutofillId getParentAutofillId();
   }
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 d3e7ea1..e352cb6 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";
@@ -920,6 +923,7 @@
     field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000
+    field public static final int MODULE_APEX_NAME = 1; // 0x1
     field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
     field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
   }
@@ -2536,6 +2540,7 @@
   }
 
   public class UserManager {
+    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
   }
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..43cfec3f 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 2237bf2..956fd29 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -44,12 +44,8 @@
 
 cc_defaults {
     name: "statsd_defaults",
-    aidl: {
-        include_dirs: ["frameworks/base/core/java"],
-    },
 
     srcs: [
-        ":statsd_aidl",
         "src/active_config_list.proto",
         "src/anomaly/AlarmMonitor.cpp",
         "src/anomaly/AlarmTracker.cpp",
@@ -64,7 +60,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",
@@ -110,7 +106,7 @@
     ],
 
     cflags: [
-        // "-DNEW_ENCODING_SCHEME",
+        "-DNEW_ENCODING_SCHEME",
     ],
 
     local_include_dirs: [
@@ -118,23 +114,20 @@
     ],
 
     static_libs: [
-        "android.frameworks.stats@1.0",
         "libbase",
         "libcutils",
-        "liblog",
         "libprotoutil",
         "libstatslog",
-        "libstatssocket",
+        "libstatsmetadata",
         "libsysutils",
+        "libutils",
     ],
     shared_libs: [
         "libbinder",
-        "libgraphicsenv",
-        "libhidlbase",
         "libincident",
-        "libservices",
-        "libstatsmetadata",
-        "libutils",
+        "liblog",
+        "libstatssocket",
+        "statsd-aidl-cpp",
     ],
 }
 
@@ -160,7 +153,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libstatsmetadata",
     host_supported: true,
     generated_sources: [
@@ -223,8 +216,6 @@
 
     shared_libs: ["libgtest_prod"],
 
-    vintf_fragments: ["android.frameworks.stats@1.0-service.xml"],
-
     init_rc: ["statsd.rc"],
 }
 
@@ -277,8 +268,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/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 5b75b97..6b9d0e4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -26,6 +26,86 @@
 using std::string;
 using std::vector;
 
+// These constants must be kept in sync with those in StatsDimensionsValue.java
+const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
+const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3;
+const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4;
+// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because
+// unused -- statsd does not correctly support bool types)
+const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6;
+const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7;
+
+/**
+ * Recursive helper function that populates a parent StatsDimensionsValueParcel
+ * with children StatsDimensionsValueParcels.
+ *
+ * \param dims vector of FieldValues stored by HashableDimensionKey
+ * \param index positions in dims vector to start reading children from
+ * \param depth level of parent parcel in the full StatsDimensionsValueParcel
+ * tree
+ */
+static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel &parentParcel,
+                                                const vector<FieldValue>& dims, size_t& index,
+                                                int depth, int prefix) {
+    while (index < dims.size()) {
+        const FieldValue& dim = dims[index];
+        int fieldDepth = dim.mField.getDepth();
+        int fieldPrefix = dim.mField.getPrefix(depth);
+        StatsDimensionsValueParcel childParcel;
+        childParcel.field = dim.mField.getPosAtDepth(depth);
+        if (depth > 2) {
+            ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel.");
+            return;
+        }
+        if (depth == fieldDepth && prefix == fieldPrefix) {
+            switch (dim.mValue.getType()) {
+                case INT:
+                    childParcel.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE;
+                    childParcel.intValue = dim.mValue.int_value;
+                    break;
+                case LONG:
+                    childParcel.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE;
+                    childParcel.longValue = dim.mValue.long_value;
+                    break;
+                case FLOAT:
+                    childParcel.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE;
+                    childParcel.floatValue = dim.mValue.float_value;
+                    break;
+                case STRING:
+                    childParcel.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE;
+                    childParcel.stringValue = String16(dim.mValue.str_value.c_str());
+                    break;
+                default:
+                    ALOGE("Encountered FieldValue with unsupported value type.");
+                    break;
+            }
+            index++;
+            parentParcel.tupleValue.push_back(childParcel);
+        } else if (fieldDepth > depth && fieldPrefix == prefix) {
+            childParcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
+            populateStatsDimensionsValueParcelChildren(childParcel, dims, index, depth + 1,
+                                                       dim.mField.getPrefix(depth + 1));
+            parentParcel.tupleValue.push_back(childParcel);
+        } else {
+            return;
+        }
+    }
+}
+
+StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const {
+    StatsDimensionsValueParcel parcel;
+    if (mValues.size() == 0) {
+        return parcel;
+    }
+
+    parcel.field = mValues[0].mField.getTag();
+    parcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
+
+    size_t index = 0;
+    populateStatsDimensionsValueParcelChildren(parcel, mValues, index, /*depth=*/0, /*prefix=*/0);
+    return parcel;
+}
+
 android::hash_t hashDimension(const HashableDimensionKey& value) {
     android::hash_t hash = 0;
     for (const auto& fieldValue : value.getValues()) {
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 654e135..4adcf96 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -16,10 +16,11 @@
 
 #pragma once
 
+#include <android/os/StatsDimensionsValueParcel.h>
 #include <utils/JenkinsHash.h>
 #include <vector>
-#include "FieldValue.h"
 #include "android-base/stringprintf.h"
+#include "FieldValue.h"
 #include "logd/LogEvent.h"
 
 namespace android {
@@ -69,6 +70,8 @@
         return nullptr;
     }
 
+    StatsDimensionsValueParcel toStatsDimensionsValueParcel() const;
+
     std::string toString() const;
 
     bool operator!=(const HashableDimensionKey& that) const;
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 879b3c3..6e7f081 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,196 @@
     }
 }
 
+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;
+    }
+    // The Get* functions don't modify the status on success, they only write in
+    // failure statuses, so we can use one status variable for all calls then
+    // check if it is no longer NO_ERROR.
+    status_t err = NO_ERROR;
+    InstallTrainInfo trainInfo;
+    trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err));
+    trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err);
+    trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err);
+    trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err);
+    trainInfo.requiresLowLatencyMonitor =
+            event->GetBool(5 /*requires low latency monitor field id*/, &err);
+    trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err));
+#ifdef NEW_ENCODING_SCHEME
+    std::vector<uint8_t> trainExperimentIdBytes =
+            event->GetStorage(7 /*experiment ids field id*/, &err);
+#else
+    string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err);
+#endif
+    bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err);
+
+    if (err != 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;
+    }
+    trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(),
+                               trainExperimentIds.experiment_id().end()};
+
+    // Update the train info on disk and get any data the logevent is missing.
+    getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo);
+
+    std::vector<uint8_t> trainExperimentIdProto;
+    writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto);
+    int32_t userId = multiuser_get_user_id(uid);
+
+    event->updateValue(2 /*train version field id*/, trainInfo.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);
+
+    if (is_rollback) {
+        int bit = trainInfo.requiresStaging ? 1 : 0;
+        event->updateValue(3 /*requires staging field id*/, bit, INT);
+        bit = trainInfo.rollbackEnabled ? 1 : 0;
+        event->updateValue(4 /*rollback enabled field id*/, bit, INT);
+        bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0;
+        event->updateValue(5 /*requires low latency monitor field id*/, bit, INT);
+    }
+}
+
+void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback,
+                                                    InstallTrainInfo* trainInfo) {
+    // If the train name is empty, we don't know which train to attribute the
+    // event to, so return early.
+    if (trainInfo->trainName.empty()) {
+        return;
+    }
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk);
+
+    bool resetExperimentIds = false;
+    if (readTrainInfoSuccess) {
+        // Keep the old train version if we received an empty version.
+        if (trainInfo->trainVersionCode == -1) {
+            trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode;
+        } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) {
+            // Reset experiment ids if we receive a new non-empty train version.
+            resetExperimentIds = true;
+        }
+
+        // Reset if we received a different experiment id.
+        if (!trainInfo->experimentIds.empty() &&
+            (trainInfoOnDisk.experimentIds.empty() ||
+             trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) {
+            resetExperimentIds = true;
+        }
+    }
+
+    // Find the right experiment IDs
+    if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) {
+        trainInfo->experimentIds = trainInfoOnDisk.experimentIds;
+    }
+
+    if (!trainInfo->experimentIds.empty()) {
+        int64_t firstId = trainInfo->experimentIds.at(0);
+        switch (trainInfo->status) {
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
+                trainInfo->experimentIds.push_back(firstId + 1);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
+                trainInfo->experimentIds.push_back(firstId + 2);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
+                trainInfo->experimentIds.push_back(firstId + 3);
+                break;
+        }
+    }
+
+    if (is_rollback) {
+        trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging;
+        trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled;
+        trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor;
+    }
+
+    StorageManager::writeTrainInfo(*trainInfo);
+}
+
+void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) {
+    pid_t pid = event->GetPid();
+    uid_t uid = event->GetUid();
+    if (!checkPermissionForIds(kPermissionDump, pid, uid) ||
+        !checkPermissionForIds(kPermissionUsage, pid, uid)) {
+        return;
+    }
+    // The Get* functions don't modify the status on success, they only write in
+    // failure statuses, so we can use one status variable for all calls then
+    // check if it is no longer NO_ERROR.
+    status_t err = NO_ERROR;
+    int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err));
+    string packageName = string(event->GetString(2 /*package name field id*/, &err));
+
+    if (err != NO_ERROR) {
+        ALOGE("Failed to parse fields in watchdog rollback occurred log event");
+        return;
+    }
+
+    vector<int64_t> experimentIds =
+        processWatchdogRollbackOccurred(rollbackType, packageName);
+    vector<uint8_t> experimentIdProto;
+    writeExperimentIdsToProto(experimentIds, &experimentIdProto);
+
+#ifdef NEW_ENCODING_SCHEME
+    event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE);
+#else
+    event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STRING);
+#endif
+}
+
+vector<int64_t> StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
+                                                                    const string& packageNameIn) {
+    // If the package name is empty, we can't attribute it to any train, so
+    // return early.
+    if (packageNameIn.empty()) {
+      return vector<int64_t>();
+    }
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk);
+
+    if (!readTrainInfoSuccess) {
+        return vector<int64_t>();
+    }
+
+    if (trainInfoOnDisk.experimentIds.empty()) {
+        return vector<int64_t>();
+    }
+    switch (rollbackTypeIn) {
+        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
+            trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 4);
+            StorageManager::writeTrainInfo(trainInfoOnDisk);
+            break;
+        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
+            trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 5);
+            StorageManager::writeTrainInfo(trainInfoOnDisk);
+            break;
+    }
+
+    return trainInfoOnDisk.experimentIds;
+}
+
 void StatsLogProcessor::resetConfigs() {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     resetConfigsLocked(getElapsedRealtimeNs());
@@ -201,6 +399,18 @@
 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);
+    }
+
+    // Hard-coded logic to update experiment ids on disk for certain rollback
+    // types and fill the rollback atom with experiment ids
+    if (event->GetTagId() == android::util::WATCHDOG_ROLLBACK_OCCURRED) {
+        onWatchdogRollbackOccurredLocked(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..42e5676 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest_prod.h>
 #include "config/ConfigListener.h"
+#include "logd/LogEvent.h"
 #include "metrics/MetricsManager.h"
 #include "packages/UidMap.h"
 #include "external/StatsPullerManager.h"
@@ -196,6 +197,22 @@
     // Handler over the isolated uid change event.
     void onIsolatedUidChangedEventLocked(const LogEvent& event);
 
+    // Handler over the binary push state changed event.
+    void onBinaryPushStateChangedEventLocked(LogEvent* event);
+
+    // Handler over the watchdog rollback occurred event.
+    void onWatchdogRollbackOccurredLocked(LogEvent* event);
+
+    // Updates train info on disk based on binary push state changed info and
+    // write disk info into parameters.
+    void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn);
+
+    // Gets experiment ids on disk for associated train and updates them
+    // depending on rollback type. Then writes them back to disk and returns
+    // them.
+    std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
+                                                          const string& packageName);
+
     // 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..168833f 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,277 +1293,27 @@
     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,
-                                                      const int32_t rollbackReasonIn,
-                                                      const android::String16&
-                                                       failingPackageNameIn) {
-    // 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));
-        }
-    }
-
-    android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED,
-            rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn,
-            rollbackReasonIn, String8(failingPackageNameIn).string());
-
-    // Fast return to save disk read.
-    if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS
-            && rollbackTypeIn !=
-                    android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE) {
-        return Status::ok();
-    }
-
-    bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfoOnDisk;
-    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
-
-    if (!readTrainInfoSuccess) {
-        return Status::ok();
-    }
-    std::vector<int64_t> experimentIds = trainInfoOnDisk.experimentIds;
-    if (experimentIds.empty()) {
-        return Status::ok();
-    }
-    switch (rollbackTypeIn) {
-        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
-            experimentIds.push_back(experimentIds[0] + 4);
-            break;
-        case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
-            experimentIds.push_back(experimentIds[0] + 5);
-            break;
-    }
-    StorageManager::writeTrainInfo(trainInfoOnDisk.trainVersionCode, trainInfoOnDisk.trainName,
-            trainInfoOnDisk.status, experimentIds);
-    return Status::ok();
-}
-
-
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
     ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
+    experimentIdsOut->clear();
     // Read the latest train info
-    InstallTrainInfo trainInfo;
-    if (!StorageManager::readTrainInfo(trainInfo)) {
+    vector<InstallTrainInfo> trainInfoList = StorageManager::readAllTrainInfo();
+    if (trainInfoList.empty()) {
         // No train info means no experiment IDs, return an empty list
-        experimentIdsOut->clear();
         return Status::ok();
     }
 
     // Copy the experiment IDs to the out vector
-    experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end());
+    for (InstallTrainInfo& trainInfo : trainInfoList) {
+        experimentIdsOut->insert(experimentIdsOut->end(),
+                                 trainInfo.experimentIds.begin(),
+                                 trainInfo.experimentIds.end());
+    }
     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..0527d43 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,85 +187,10 @@
     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(
-            const int32_t rollbackTypeIn,
-            const android::String16& packageNameIn,
-            const int64_t packageVersionCodeIn,
-            const int32_t rollbackReasonIn,
-            const android::String16& failingPackageNameIn) override;
-
-    /**
      * Binder call to get registered experiment IDs.
      */
     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..89b1798 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -127,7 +127,7 @@
         WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"];
         AnomalyDetected anomaly_detected = 46;
         AppBreadcrumbReported app_breadcrumb_reported =
-                47 [(allow_from_any_uid) = true, (module) = "framework"];
+                47 [(allow_from_any_uid) = true, (module) = "statsd"];
         AppStartOccurred app_start_occurred = 48 [(module) = "framework"];
         AppStartCanceled app_start_canceled = 49 [(module) = "framework"];
         AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"];
@@ -188,7 +188,7 @@
         ServiceStateChanged service_state_changed = 99 [(module) = "framework"];
         ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"];
         FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"];
-        BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "framework"];
+        BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"];
         DevicePolicyEvent device_policy_event = 103 [(module) = "framework"];
         DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"];
         DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported =
@@ -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,8 @@
         WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"];
         WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"];
         WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
+        AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"];
+        SdkExtensionStatus sdk_extension_status = 354;
     }
 
     // Pulled events will start at field 10000.
@@ -1258,6 +1261,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.
@@ -1870,6 +1875,8 @@
     // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback
     // is initiated. Empty if the package is unknown.
     optional string failing_package_name = 5;
+
+    optional TrainExperimentIds experiment_ids = 6 [(log_mode) = MODE_BYTES];
 }
 
 /**
@@ -3706,6 +3713,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;
@@ -3765,6 +3773,8 @@
     // user id
     optional int32 user_id = 8;
     optional int32 reason = 9;
+    // Whether or not this is a rollback event
+    optional bool is_rollback = 10;
 }
 
 /* Test atom, is not logged anywhere */
@@ -7509,6 +7519,9 @@
 
     // Button clicked by user - same as bit flags in buttons_presented with only single bit set
     optional int32 button_clicked = 5;
+
+    // id which identifies single session of user interacting with permission controller
+    optional int64 session_id = 6;
 }
 
 /**
@@ -8333,3 +8346,52 @@
     // 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;
+}
+
+/**
+ * Logs when an app is frozen or unfrozen.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
+ */
+message AppFreezeChanged {
+  // The type of event.
+  enum Action {
+    UNKNOWN = 0;
+    FREEZE_APP = 1;
+    UNFREEZE_APP = 2;
+  }
+  optional Action action = 1;
+
+  // Pid of the process being frozen.
+  optional int32 pid = 2;
+
+  // Name of the process being frozen.
+  optional string process_name = 3;
+
+  // Time since last unfrozen.
+  optional int64 time_unfrozen_millis = 4;
+}
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/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp
index 9d09242..a7d8d4e 100644
--- a/cmds/statsd/src/external/TrainInfoPuller.cpp
+++ b/cmds/statsd/src/external/TrainInfoPuller.cpp
@@ -37,14 +37,16 @@
 }
 
 bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    InstallTrainInfo trainInfo;
-    bool ret = StorageManager::readTrainInfo(trainInfo);
-    if (!ret) {
-        ALOGW("Failed to read train info.");
-        return false;
+    vector<InstallTrainInfo> trainInfoList =
+        StorageManager::readAllTrainInfo();
+    if (trainInfoList.empty()) {
+        ALOGW("Train info was empty.");
+        return true;
     }
-    auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo);
-    data->push_back(event);
+    for (InstallTrainInfo& trainInfo : trainInfoList) {
+        auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo);
+        data->push_back(event);
+    }
     return true;
 }
 
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..a67bef4 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 {
@@ -59,6 +56,9 @@
     std::string trainName;
     int32_t status;
     std::vector<int64_t> experimentIds;
+    bool requiresStaging;
+    bool rollbackEnabled;
+    bool requiresLowLatencyMonitor;
 };
 
 /**
@@ -103,9 +103,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 +141,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 +212,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/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 507297c..1bac19e 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -44,7 +44,7 @@
 #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin"
 
 // Magic word at the start of the train info file, change this if changing the file format
-const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff;
+const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf;
 
 // for ConfigMetricsReportList
 const int FIELD_ID_REPORTS = 2;
@@ -75,6 +75,29 @@
                         (long long)id);
 }
 
+static const char* findTrainInfoFileNameLocked(const string& trainName) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
+    if (dir == NULL) {
+        VLOG("Path %s does not exist", TRAIN_INFO_DIR);
+        return nullptr;
+    }
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* fileName = de->d_name;
+        if (fileName[0] == '.') continue;
+
+        size_t fileNameLength = strlen(fileName);
+        if (fileNameLength >= trainName.length()) {
+            if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(),
+                             trainName.length())) {
+                return fileName;
+            }
+        }
+    }
+
+    return nullptr;
+}
+
 // Returns array of int64_t which contains timestamp in seconds, uid,
 // configID and whether the file is a local history file.
 static void parseFileName(char* name, FileName* output) {
@@ -123,20 +146,25 @@
     close(fd);
 }
 
-bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                                    int32_t status, const std::vector<int64_t>& experimentIds) {
+bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) {
     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
 
-    deleteAllFiles(TRAIN_INFO_DIR);
+    if (trainInfo.trainName.empty()) {
+      return false;
+    }
+    deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str());
 
-    int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
+    std::string fileName =
+            StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(),
+                         trainInfo.trainName.c_str());
+
+    int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
     if (fd == -1) {
-        VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH);
+        VLOG("Attempt to access %s but failed", fileName.c_str());
         return false;
     }
 
     size_t result;
-
     // Write the magic word
     result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC));
     if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) {
@@ -146,8 +174,8 @@
     }
 
     // Write the train version
-    const size_t trainVersionCodeByteCount = sizeof(trainVersionCode);
-    result = write(fd, &trainVersionCode, trainVersionCodeByteCount);
+    const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode);
+    result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
     if (result != trainVersionCodeByteCount) {
         VLOG("Failed to wrtie train version code");
         close(fd);
@@ -155,7 +183,7 @@
     }
 
     // Write # of bytes in trainName to file
-    const size_t trainNameSize = trainName.size();
+    const size_t trainNameSize = trainInfo.trainName.size();
     const size_t trainNameSizeByteCount = sizeof(trainNameSize);
     result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount);
     if (result != trainNameSizeByteCount) {
@@ -165,7 +193,7 @@
     }
 
     // Write trainName to file
-    result = write(fd, trainName.c_str(), trainNameSize);
+    result = write(fd, trainInfo.trainName.c_str(), trainNameSize);
     if (result != trainNameSize) {
         VLOG("Failed to write train name");
         close(fd);
@@ -173,8 +201,8 @@
     }
 
     // Write status to file
-    const size_t statusByteCount = sizeof(status);
-    result = write(fd, (uint8_t*)&status, statusByteCount);
+    const size_t statusByteCount = sizeof(trainInfo.status);
+    result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount);
     if (result != statusByteCount) {
         VLOG("Failed to write status");
         close(fd);
@@ -182,7 +210,7 @@
     }
 
     // Write experiment id count to file.
-    const size_t experimentIdsCount = experimentIds.size();
+    const size_t experimentIdsCount = trainInfo.experimentIds.size();
     const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount);
     result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount);
     if (result != experimentIdsCountByteCount) {
@@ -193,7 +221,7 @@
 
     // Write experimentIds to file
     for (size_t i = 0; i < experimentIdsCount; i++) {
-        const int64_t experimentId = experimentIds[i];
+        const int64_t experimentId = trainInfo.experimentIds[i];
         const size_t experimentIdByteCount = sizeof(experimentId);
         result = write(fd, &experimentId, experimentIdByteCount);
         if (result == experimentIdByteCount) {
@@ -205,23 +233,47 @@
         }
     }
 
-    result = fchown(fd, AID_STATSD, AID_STATSD);
-    if (result) {
-        VLOG("Failed to chown train info file to statsd");
-        close(fd);
-        return false;
+    // Write bools to file
+    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
+    result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write requires staging");
+      close(fd);
+      return false;
+    }
+
+    result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write rollback enabled");
+      close(fd);
+      return false;
+    }
+
+    result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount);
+    if (result != boolByteCount) {
+      VLOG("Failed to write requires log latency monitor");
+      close(fd);
+      return false;
     }
 
     close(fd);
     return true;
 }
 
-bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) {
+bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) {
     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
+    return readTrainInfoLocked(trainName, trainInfo);
+}
 
-    int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC);
+bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) {
+    trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true);
+    const char* fileName = findTrainInfoFileNameLocked(trainName);
+    if (fileName == nullptr) {
+        return false;
+    }
+    int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName).c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
-        VLOG("Failed to open train-info.bin");
+        VLOG("Failed to open %s", fileName);
         return false;
     }
 
@@ -297,6 +349,29 @@
         trainInfo.experimentIds.push_back(experimentId);
     }
 
+    // Read bools
+    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
+    result = read(fd, &trainInfo.requiresStaging, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires requires staging from train info file");
+        close(fd);
+        return false;
+    }
+
+    result = read(fd, &trainInfo.rollbackEnabled, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires rollback enabled from train info file");
+        close(fd);
+        return false;
+    }
+
+    result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount);
+    if (result != boolByteCount) {
+        VLOG("Failed to read requires requires low latency monitor from train info file");
+        close(fd);
+        return false;
+    }
+
     // Expect to be at EOF.
     char c;
     result = read(fd, &c, 1);
@@ -311,6 +386,32 @@
     return true;
 }
 
+vector<InstallTrainInfo> StorageManager::readAllTrainInfo() {
+    std::lock_guard<std::mutex> lock(sTrainInfoMutex);
+    vector<InstallTrainInfo> trainInfoList;
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
+    if (dir == NULL) {
+        VLOG("Directory does not exist: %s", TRAIN_INFO_DIR);
+        return trainInfoList;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') {
+            continue;
+        }
+
+        InstallTrainInfo trainInfo;
+        bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo);
+        if (!readSuccess) {
+            continue;
+        }
+        trainInfoList.push_back(trainInfo);
+    }
+    return trainInfoList;
+}
+
 void StorageManager::deleteFile(const char* file) {
     if (remove(file) != 0) {
         VLOG("Attempt to delete %s but is not found", file);
@@ -574,7 +675,7 @@
     });
 }
 
-void StorageManager::trimToFit(const char* path) {
+void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) {
     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
     if (dir == NULL) {
         VLOG("Path %s does not exist", path);
@@ -589,7 +690,12 @@
         if (name[0] == '.') continue;
 
         FileName output;
-        parseFileName(name, &output);
+        if (parseTimestampOnly) {
+            output.mTimestampSec = StrToInt64(strtok(name, "_"));
+            output.mIsHistory = false;
+        } else {
+            parseFileName(name, &output);
+        }
         if (output.mTimestampSec == -1) continue;
         string file_name = output.getFullFileName(path);
 
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index 69b41c2..d59046d 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -52,13 +52,22 @@
     /**
      * Writes train info.
      */
-    static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
-                               int32_t status, const std::vector<int64_t>& experimentIds);
+    static bool writeTrainInfo(const InstallTrainInfo& trainInfo);
 
     /**
      * Reads train info.
      */
-    static bool readTrainInfo(InstallTrainInfo& trainInfo);
+    static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo);
+
+    /**
+     * Reads train info assuming lock is obtained.
+     */
+    static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo);
+
+    /**
+     * Reads all train info and returns a vector of train info.
+     */
+    static vector<InstallTrainInfo> readAllTrainInfo();
 
     /**
      * Reads the file content to the buffer.
@@ -124,7 +133,7 @@
      * Trims files in the provided directory to limit the total size, number of
      * files, accumulation of outdated files.
      */
-    static void trimToFit(const char* dir);
+    static void trimToFit(const char* dir, bool parseTimestampOnly = false);
 
     /**
      * Returns true if there already exists identical configuration on device.
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/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 160b57e..8fd6b46 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -121,7 +121,7 @@
             subscription.id(),
             subscription.rule_id(),
             cookies,
-            getStatsDimensionsValue(dimKey.getDimensionKeyInWhat()));
+            dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel());
 }
 
 sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
@@ -138,61 +138,6 @@
     return pirMapIt->second;
 }
 
-void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth,
-                                   int prefix, vector<StatsDimensionsValue>* output) {
-    size_t count = dims.size();
-    while (*index < count) {
-        const auto& dim = dims[*index];
-        const int valueDepth = dim.mField.getDepth();
-        const int valuePrefix = dim.mField.getPrefix(depth);
-        if (valueDepth > 2) {
-            ALOGE("Depth > 2 not supported");
-            return;
-        }
-        if (depth == valueDepth && valuePrefix == prefix) {
-            switch (dim.mValue.getType()) {
-                case INT:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.int_value));
-                    break;
-                case LONG:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.long_value));
-                    break;
-                case FLOAT:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           dim.mValue.float_value));
-                    break;
-                case STRING:
-                    output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth),
-                                                           String16(dim.mValue.str_value.c_str())));
-                    break;
-                default:
-                    break;
-            }
-            (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
-            vector<StatsDimensionsValue> childOutput;
-            getStatsDimensionsValueHelper(dims, index, depth + 1, dim.mField.getPrefix(depth + 1),
-                                          &childOutput);
-            output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), childOutput));
-        } else {
-            return;
-        }
-    }
-}
-
-StatsDimensionsValue SubscriberReporter::getStatsDimensionsValue(const HashableDimensionKey& dim) {
-    if (dim.getValues().size() == 0) {
-        return StatsDimensionsValue();
-    }
-
-    vector<StatsDimensionsValue> fields;
-    size_t index = 0;
-    getStatsDimensionsValueHelper(dim.getValues(), &index, 0, 0, &fields);
-    return StatsDimensionsValue(dim.getValues()[0].mField.getTag(), fields);
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 087a1b8..42599f5 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -22,7 +22,6 @@
 
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // subscription
-#include "android/os/StatsDimensionsValue.h"
 #include "HashableDimensionKey.h"
 
 #include <mutex>
@@ -70,8 +69,6 @@
 
     sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
 
-    static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim);
-
 private:
     SubscriberReporter() {};
 
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index f4a59ed..9e69d97 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -290,33 +290,34 @@
     }
 }
 
-TEST(AtomMatcherTest, TestSubscriberDimensionWrite) {
-    HashableDimensionKey dim;
-
-    int pos1[] = {1, 1, 1};
-    int pos2[] = {1, 1, 2};
-    int pos3[] = {1, 1, 3};
-    int pos4[] = {2, 0, 0};
-
-    Field field1(10, pos1, 2);
-    Field field2(10, pos2, 2);
-    Field field3(10, pos3, 2);
-    Field field4(10, pos4, 0);
-
-    Value value1((int32_t)10025);
-    Value value2("tag");
-    Value value3((int32_t)987654);
-    Value value4((int32_t)99999);
-
-    dim.addValue(FieldValue(field1, value1));
-    dim.addValue(FieldValue(field2, value2));
-    dim.addValue(FieldValue(field3, value3));
-    dim.addValue(FieldValue(field4, value4));
-
-    SubscriberReporter::getStatsDimensionsValue(dim);
-    // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't
-    // have any read api.
-}
+//TODO(b/149050405) Update this test for StatsDimensionValueParcel
+//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) {
+//    HashableDimensionKey dim;
+//
+//    int pos1[] = {1, 1, 1};
+//    int pos2[] = {1, 1, 2};
+//    int pos3[] = {1, 1, 3};
+//    int pos4[] = {2, 0, 0};
+//
+//    Field field1(10, pos1, 2);
+//    Field field2(10, pos2, 2);
+//    Field field3(10, pos3, 2);
+//    Field field4(10, pos4, 0);
+//
+//    Value value1((int32_t)10025);
+//    Value value2("tag");
+//    Value value3((int32_t)987654);
+//    Value value4((int32_t)99999);
+//
+//    dim.addValue(FieldValue(field1, value1));
+//    dim.addValue(FieldValue(field2, value2));
+//    dim.addValue(FieldValue(field3, value3));
+//    dim.addValue(FieldValue(field4, value4));
+//
+//    SubscriberReporter::getStatsDimensionsValue(dim);
+//    // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't
+//    // have any read api.
+//}
 
 TEST(AtomMatcherTest, TestWriteDimensionToProto) {
     HashableDimensionKey dim;
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/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp
index b91e5a0..27a86e42 100644
--- a/cmds/statsd/tests/storage/StorageManager_test.cpp
+++ b/cmds/statsd/tests/storage/StorageManager_test.cpp
@@ -40,40 +40,12 @@
 
     bool result;
 
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
+    result = StorageManager::writeTrainInfo(trainInfo);
 
     EXPECT_TRUE(result);
 
     InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
-    EXPECT_TRUE(result);
-
-    EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
-    EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size());
-    EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName);
-    EXPECT_EQ(trainInfo.status, trainInfoResult.status);
-    EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size());
-    EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
-}
-
-TEST(StorageManagerTest, TrainInfoReadWriteEmptyTrainNameTest) {
-    InstallTrainInfo trainInfo;
-    trainInfo.trainVersionCode = 12345;
-    trainInfo.trainName = "";
-    trainInfo.status = 1;
-    const char* expIds = "test_ids";
-    trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds));
-
-    bool result;
-
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
-
-    EXPECT_TRUE(result);
-
-    InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
+    result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult);
     EXPECT_TRUE(result);
 
     EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
@@ -94,13 +66,12 @@
 
     bool result;
 
-    result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName,
-                                            trainInfo.status, trainInfo.experimentIds);
+    result = StorageManager::writeTrainInfo(trainInfo);
 
     EXPECT_TRUE(result);
 
     InstallTrainInfo trainInfoResult;
-    result = StorageManager::readTrainInfo(trainInfoResult);
+    result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult);
     EXPECT_TRUE(result);
 
     EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode);
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..7ee4405 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -94,6 +94,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 
@@ -1472,7 +1473,7 @@
                 dest.writeInt(1);
                 dest.writeString(mLabel);
             }
-            if (mIcon == null) {
+            if (mIcon == null || mIcon.isRecycled()) {
                 dest.writeInt(0);
             } else {
                 dest.writeInt(1);
@@ -3555,13 +3556,13 @@
      * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in
      *         the order from most recent to least recent.
      */
-    @Nullable
+    @NonNull
     public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName,
             @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) {
         try {
             ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons(
                     packageName, pid, maxNum, mContext.getUserId());
-            return r == null ? null : r.getList();
+            return r == null ? Collections.emptyList() : r.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
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..fc37af9 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)
@@ -3865,7 +3916,10 @@
         }
 
         /**
-         * The features that have been used when checking the op
+         * The features that have been used when checking the op keyed by id of the feature.
+         *
+         * @see Context#createFeatureContext(String)
+         * @see #noteOp(String, int, String, String, String)
          */
         @DataClass.Generated.Member
         public @NonNull Map<String,OpFeatureEntry> getFeatures() {
@@ -6774,6 +6828,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 +6855,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 +7817,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/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 4bf5f07..c55453e 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.app.ActivityManager.RunningAppProcessInfo.Importance;
 import android.icu.text.SimpleDateFormat;
 import android.os.Parcel;
@@ -245,12 +244,12 @@
     /**
      * @see {@link #getPss}
      */
-    private int mPss;
+    private long mPss;
 
     /**
      * @see {@link #getRss}
      */
-    private int mRss;
+    private long mRss;
 
     /**
      * @see {@link #getTimestamp}
@@ -385,7 +384,7 @@
      * it's NOT the exact memory information prior to its death; and it'll be zero
      * if the process died before system had a chance to take the sample. </p>
      */
-    public int getPss() {
+    public long getPss() {
         return mPss;
     }
 
@@ -396,12 +395,13 @@
      * it's NOT the exact memory information prior to its death; and it'll be zero
      * if the process died before system had a chance to take the sample. </p>
      */
-    public int getRss() {
+    public long getRss() {
         return mRss;
     }
 
     /**
-     * The timestamp of the process's death, in milliseconds since the epoch.
+     * The timestamp of the process's death, in milliseconds since the epoch,
+     * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}.
      */
     public long getTimestamp() {
         return mTimestamp;
@@ -409,6 +409,9 @@
 
     /**
      * The human readable description of the process's death, given by the system; could be null.
+     *
+     * <p class="note">Note: only intended to be human-readable and the system provides no
+     * guarantees that the format is stable across devices or Android releases.</p>
      */
     public @Nullable String getDescription() {
         return mDescription;
@@ -416,10 +419,7 @@
 
     /**
      * Return the user id of the record on a multi-user system.
-     *
-     * @hide
      */
-    @SystemApi
     public @NonNull UserHandle getUserHandle() {
         return UserHandle.of(UserHandle.getUserId(mRealUid));
     }
@@ -546,7 +546,7 @@
      *
      * @hide
      */
-    public void setPss(final int pss) {
+    public void setPss(final long pss) {
         mPss = pss;
     }
 
@@ -555,7 +555,7 @@
      *
      * @hide
      */
-    public void setRss(final int rss) {
+    public void setRss(final long rss) {
         mRss = rss;
     }
 
@@ -630,8 +630,8 @@
         dest.writeInt(mSubReason);
         dest.writeInt(mStatus);
         dest.writeInt(mImportance);
-        dest.writeInt(mPss);
-        dest.writeInt(mRss);
+        dest.writeLong(mPss);
+        dest.writeLong(mRss);
         dest.writeLong(mTimestamp);
         dest.writeString(mDescription);
     }
@@ -669,8 +669,8 @@
         mSubReason = in.readInt();
         mStatus = in.readInt();
         mImportance = in.readInt();
-        mPss = in.readInt();
-        mRss = in.readInt();
+        mPss = in.readLong();
+        mRss = in.readLong();
         mTimestamp = in.readLong();
         mDescription = in.readString();
     }
@@ -848,10 +848,10 @@
                     mImportance = proto.readInt(ApplicationExitInfoProto.IMPORTANCE);
                     break;
                 case (int) ApplicationExitInfoProto.PSS:
-                    mPss = proto.readInt(ApplicationExitInfoProto.PSS);
+                    mPss = proto.readLong(ApplicationExitInfoProto.PSS);
                     break;
                 case (int) ApplicationExitInfoProto.RSS:
-                    mRss = proto.readInt(ApplicationExitInfoProto.RSS);
+                    mRss = proto.readLong(ApplicationExitInfoProto.RSS);
                     break;
                 case (int) ApplicationExitInfoProto.TIMESTAMP:
                     mTimestamp = proto.readLong(ApplicationExitInfoProto.TIMESTAMP);
@@ -891,8 +891,8 @@
         result = 31 * result + mSubReason;
         result = 31 * result + mImportance;
         result = 31 * result + mStatus;
-        result = 31 * result + mPss;
-        result = 31 * result + mRss;
+        result = 31 * result + (int) mPss;
+        result = 31 * result + (int) mRss;
         result = 31 * result + Long.hashCode(mTimestamp);
         result = 31 * result + Objects.hashCode(mProcessName);
         result = 31 * result + Objects.hashCode(mDescription);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 4b1ba02..1d4a1ac 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);
 
@@ -86,6 +88,7 @@
     void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
     void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
     void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList);
+    ParceledListSlice getConversationsForPackage(String pkg, int uid);
     ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted);
     NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid);
     NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted);
@@ -94,7 +97,7 @@
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
     NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
     void createConversationNotificationChannelForPackage(String pkg, int uid, String triggeringKey, in NotificationChannel parentChannel, String conversationId);
-    NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
+    NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
     void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
@@ -111,6 +114,7 @@
     int getAppsBypassingDndCount(int uid);
     ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
     boolean isPackagePaused(String pkg);
+    void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
 
     void silenceNotificationSound();
 
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl
index 168f782..5d5956e 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);
@@ -40,6 +51,9 @@
     /** Deletes a persistent root task in WM */
     boolean deleteRootTask(IWindowContainer task);
 
+    /** Gets direct child tasks (ordered from top-to-bottom) */
+    List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent);
+
     /** Get the root task which contains the current ime target */
     IWindowContainer getImeTarget(int display);
 
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index 909a476..f26e628 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -346,6 +346,26 @@
     }
 
     /**
+     * Removes an individual historical notification and regenerates the string pool
+     */
+    public boolean removeNotificationFromWrite(String packageName, long postedTime) {
+        boolean removed = false;
+        for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
+            HistoricalNotification hn = mNotificationsToWrite.get(i);
+            if (packageName.equals(hn.getPackage())
+                    && postedTime == hn.getPostedTimeMs()) {
+                removed = true;
+                mNotificationsToWrite.remove(i);
+            }
+        }
+        if (removed) {
+            poolStringsFromNotifications();
+        }
+
+        return removed;
+    }
+
+    /**
      * Gets pooled strings in order to write them to disk
      */
     public @NonNull String[] getPooledStringsToWrite() {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6f1effd..5f74d2e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -598,7 +598,8 @@
      *     is not able to access the wallpaper.
      */
     public Drawable getDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -829,7 +830,8 @@
      * null pointer if these is none.
      */
     public Drawable peekDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -853,7 +855,8 @@
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public Drawable getFastDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -869,7 +872,8 @@
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public Drawable peekFastDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -892,10 +896,11 @@
         if (!shouldEnableWideColorGamut()) {
             return false;
         }
-        Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy);
         return bitmap != null && bitmap.getColorSpace() != null
                 && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB)
-                && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace());
+                && cmProxy.isSupportedColorSpace(bitmap.getColorSpace());
     }
 
     /**
@@ -928,8 +933,8 @@
      * @hide
      */
     public Bitmap getBitmapAsUser(int userId, boolean hardware) {
-        return sGlobals.peekWallpaperBitmap(
-                mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy);
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware, cmProxy);
     }
 
     /**
@@ -2074,12 +2079,23 @@
     }
 
     /**
-     * A private class to help Globals#getCurrentWallpaperLocked handle color management.
+     * Get the instance of {@link ColorManagementProxy}.
+     *
+     * @return instance of {@link ColorManagementProxy}.
+     * @hide
      */
-    private static class ColorManagementProxy {
+    public ColorManagementProxy getColorManagementProxy() {
+        return mCmProxy;
+    }
+
+    /**
+     * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management.
+     * @hide
+     */
+    public static class ColorManagementProxy {
         private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>();
 
-        ColorManagementProxy(Context context) {
+        public ColorManagementProxy(@NonNull Context context) {
             // Get a list of supported wide gamut color spaces.
             Display display = context.getDisplay();
             if (display != null) {
@@ -2087,9 +2103,14 @@
             }
         }
 
+        @NonNull
+        public Set<ColorSpace> getSupportedColorSpaces() {
+            return mSupportedColorSpaces;
+        }
+
         boolean isSupportedColorSpace(ColorSpace colorSpace) {
             return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)
-                    || mSupportedColorSpaces.contains(colorSpace));
+                    || getSupportedColorSpaces().contains(colorSpace));
         }
 
         void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) {
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 36ae450..d279983 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -60,12 +60,12 @@
         if (token != null && !isWindowToken(token)) {
             throw new IllegalArgumentException("Token must be registered to server.");
         }
+        mToken = token != null ? token : new Binder();
 
-        final ContextImpl contextImpl = createBaseWindowContext(base, token);
+        final ContextImpl contextImpl = createBaseWindowContext(base, mToken);
         attachBaseContext(contextImpl);
         contextImpl.setOuterContext(this);
 
-        mToken = token != null ? token : new Binder();
         mDisplayId = getDisplayId();
         mWindowManager = new WindowManagerImpl(this);
         mWindowManager.setDefaultToken(mToken);
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 7599791..9cec514 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9462,16 +9462,6 @@
      * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to
      * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted.
      *
-     * NOTE: Starting from Android R, location-related permissions cannot be granted by the
-     * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the
-     * following permissions will return false:
-     *
-     * <ul>
-     * <li>{@code ACCESS_FINE_LOCATION}</li>
-     * <li>{@code ACCESS_BACKGROUND_LOCATION}</li>
-     * <li>{@code ACCESS_COARSE_LOCATION}</li>
-     * </ul>
-     *
      * @param admin Which profile or device owner this request is associated with.
      * @param packageName The application to grant or revoke a permission to.
      * @param permission The permission to grant or revoke.
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..e289a27
--- /dev/null
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -0,0 +1,115 @@
+/*
+ * 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.Binder;
+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));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * 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));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return platformCompat.isChangeEnabledByUid(changeId, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
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 79%
rename from core/java/android/app/timedetector/PhoneTimeSuggestion.java
rename to core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 0133a44..c0e8957 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -50,17 +50,17 @@
  *
  * @hide
  */
-public final class PhoneTimeSuggestion implements Parcelable {
+public final class TelephonyTimeSuggestion implements Parcelable {
 
     /** @hide */
-    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];
                 }
             };
 
@@ -68,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")
@@ -102,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;
@@ -111,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() {
@@ -121,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() {
@@ -132,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) {
@@ -144,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) {
@@ -161,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);
     }
@@ -173,7 +173,7 @@
 
     @Override
     public String toString() {
-        return "PhoneTimeSuggestion{"
+        return "TelephonyTimeSuggestion{"
                 + "mSlotIndex='" + mSlotIndex + '\''
                 + ", mUtcTime=" + mUtcTime
                 + ", mDebugInfo=" + mDebugInfo
@@ -181,7 +181,7 @@
     }
 
     /**
-     * Builds {@link PhoneTimeSuggestion} instances.
+     * Builds {@link TelephonyTimeSuggestion} instances.
      *
      * @hide
      */
@@ -193,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;
@@ -202,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) {
@@ -218,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) {
@@ -229,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 df4f513..84ad495 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -45,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 84%
rename from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
rename to core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
index 9147b44..150c01d 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
@@ -56,18 +56,18 @@
  *
  * @hide
  */
-public final class PhoneTimeZoneSuggestion implements Parcelable {
+public final class TelephonyTimeZoneSuggestion implements Parcelable {
 
     /** @hide */
     @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];
                 }
             };
 
@@ -76,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();
     }
@@ -144,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;
@@ -153,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);
         }
@@ -185,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;
@@ -195,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() {
@@ -206,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() {
@@ -216,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() {
@@ -226,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() {
@@ -237,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) {
@@ -249,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) {
@@ -266,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
@@ -280,7 +281,7 @@
 
     @Override
     public String toString() {
-        return "PhoneTimeZoneSuggestion{"
+        return "TelephonyTimeZoneSuggestion{"
                 + "mSlotIndex=" + mSlotIndex
                 + ", mZoneId='" + mZoneId + '\''
                 + ", mMatchType=" + mMatchType
@@ -290,7 +291,7 @@
     }
 
     /**
-     * Builds {@link PhoneTimeZoneSuggestion} instances.
+     * Builds {@link TelephonyTimeZoneSuggestion} instances.
      *
      * @hide
      */
@@ -304,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;
@@ -313,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) {
@@ -324,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) {
@@ -335,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) {
@@ -346,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) {
@@ -384,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 6a3953e..20761ad 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -47,8 +47,8 @@
      *
      * @hide
      */
-    @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 f398474..587c92e 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1871,15 +1871,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();
@@ -1896,15 +1900,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/ContentResolver.java b/core/java/android/content/ContentResolver.java
index f32a4ab..0e0161f 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -700,6 +700,27 @@
     /** @hide */
     public static final String REMOTE_CALLBACK_RESULT = "result";
 
+    /**
+     * How long we wait for an attached process to publish its content providers
+     * before we decide it must be hung.
+     * @hide
+     */
+    public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000;
+
+    /**
+     * How long we wait for an provider to be published. Should be longer than
+     * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
+     * @hide
+     */
+    public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS =
+            CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000;
+
+    // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
+    // long ActivityManagerService is giving a content provider to get published if a new process
+    // needs to be started for that.
+    private static final int GET_TYPE_TIMEOUT_MILLIS =
+            CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000;
+
     public ContentResolver(@Nullable Context context) {
         this(context, null);
     }
@@ -849,8 +870,6 @@
         }
     }
 
-    private static final int GET_TYPE_TIMEOUT_MILLIS = 3000;
-
     private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
         @GuardedBy("this")
         public boolean done;
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index 351edc9..cd5117b 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -33,18 +34,18 @@
 public final class AppInstallMetadata {
     private final String mPackageName;
     // Raw string encoding for the SHA-256 hash of the certificate of the app.
-    private final String mAppCertificate;
+    private final List<String> mAppCertificates;
     private final String mInstallerName;
     // Raw string encoding for the SHA-256 hash of the certificate of the installer.
-    private final String mInstallerCertificate;
+    private final List<String> mInstallerCertificates;
     private final long mVersionCode;
     private final boolean mIsPreInstalled;
 
     private AppInstallMetadata(Builder builder) {
         this.mPackageName = builder.mPackageName;
-        this.mAppCertificate = builder.mAppCertificate;
+        this.mAppCertificates = builder.mAppCertificates;
         this.mInstallerName = builder.mInstallerName;
-        this.mInstallerCertificate = builder.mInstallerCertificate;
+        this.mInstallerCertificates = builder.mInstallerCertificates;
         this.mVersionCode = builder.mVersionCode;
         this.mIsPreInstalled = builder.mIsPreInstalled;
     }
@@ -55,8 +56,8 @@
     }
 
     @NonNull
-    public String getAppCertificate() {
-        return mAppCertificate;
+    public List<String> getAppCertificates() {
+        return mAppCertificates;
     }
 
     @NonNull
@@ -65,8 +66,8 @@
     }
 
     @NonNull
-    public String getInstallerCertificate() {
-        return mInstallerCertificate;
+    public List<String> getInstallerCertificates() {
+        return mInstallerCertificates;
     }
 
     /** @see AppInstallMetadata.Builder#setVersionCode(long) */
@@ -82,12 +83,12 @@
     @Override
     public String toString() {
         return String.format(
-                "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s,"
-                    + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }",
+                "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s,"
+                    + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b }",
                 mPackageName,
-                mAppCertificate,
+                mAppCertificates,
                 mInstallerName == null ? "null" : mInstallerName,
-                mInstallerCertificate == null ? "null" : mInstallerCertificate,
+                mInstallerCertificates == null ? "null" : mInstallerCertificates,
                 mVersionCode,
                 mIsPreInstalled);
     }
@@ -95,9 +96,9 @@
     /** Builder class for constructing {@link AppInstallMetadata} objects. */
     public static final class Builder {
         private String mPackageName;
-        private String mAppCertificate;
+        private List<String> mAppCertificates;
         private String mInstallerName;
-        private String mInstallerCertificate;
+        private List<String> mInstallerCertificates;
         private long mVersionCode;
         private boolean mIsPreInstalled;
 
@@ -118,11 +119,11 @@
          * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
          * of the app.
          *
-         * @see AppInstallMetadata#getAppCertificate()
+         * @see AppInstallMetadata#getAppCertificates()
          */
         @NonNull
-        public Builder setAppCertificate(@NonNull String appCertificate) {
-            this.mAppCertificate = Objects.requireNonNull(appCertificate);
+        public Builder setAppCertificates(@NonNull List<String> appCertificates) {
+            this.mAppCertificates = Objects.requireNonNull(appCertificates);
             return this;
         }
 
@@ -143,11 +144,11 @@
          * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
          * of the installer.
          *
-         * @see AppInstallMetadata#getInstallerCertificate()
+         * @see AppInstallMetadata#getInstallerCertificates()
          */
         @NonNull
-        public Builder setInstallerCertificate(@NonNull String installerCertificate) {
-            this.mInstallerCertificate = Objects.requireNonNull(installerCertificate);
+        public Builder setInstallerCertificates(@NonNull List<String> installerCertificates) {
+            this.mInstallerCertificates = Objects.requireNonNull(installerCertificates);
             return this;
         }
 
@@ -181,7 +182,7 @@
         @NonNull
         public AppInstallMetadata build() {
             Objects.requireNonNull(mPackageName);
-            Objects.requireNonNull(mAppCertificate);
+            Objects.requireNonNull(mAppCertificates);
             return new AppInstallMetadata(this);
         }
     }
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index 439d536..42459779 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -30,6 +30,8 @@
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -332,9 +334,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 +353,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) {
@@ -381,7 +389,7 @@
             if (mValue == null || mIsHashedValue == null) {
                 return false;
             }
-            return getStringMetadataValue(appInstallMetadata, getKey()).equals(mValue);
+            return getMetadataValue(appInstallMetadata, getKey()).contains(mValue);
         }
 
         @Override
@@ -442,17 +450,17 @@
             return mIsHashedValue;
         }
 
-        private static String getStringMetadataValue(
+        private static List<String> getMetadataValue(
                 AppInstallMetadata appInstallMetadata, int key) {
             switch (key) {
                 case AtomicFormula.PACKAGE_NAME:
-                    return appInstallMetadata.getPackageName();
+                    return Collections.singletonList(appInstallMetadata.getPackageName());
                 case AtomicFormula.APP_CERTIFICATE:
-                    return appInstallMetadata.getAppCertificate();
+                    return appInstallMetadata.getAppCertificates();
                 case AtomicFormula.INSTALLER_CERTIFICATE:
-                    return appInstallMetadata.getInstallerCertificate();
+                    return appInstallMetadata.getInstallerCertificates();
                 case AtomicFormula.INSTALLER_NAME:
-                    return appInstallMetadata.getInstallerName();
+                    return Collections.singletonList(appInstallMetadata.getInstallerName());
                 default:
                     throw new IllegalStateException(
                             "Unexpected key in StringAtomicFormula: " + key);
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..38a9ac4a 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,15 @@
     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);
+
+    void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
+            in UserHandle user);
+    void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
+            in UserHandle user);
 }
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..d253278 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -34,6 +35,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 +63,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 +160,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 +417,9 @@
         List<String> mShortcutIds;
 
         @Nullable
+        List<LocusId> mLocusIds;
+
+        @Nullable
         ComponentName mActivity;
 
         @QueryFlags
@@ -442,6 +456,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 +496,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 +1040,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();
@@ -974,6 +1090,61 @@
     }
 
     /**
+     * Mark shortcuts as cached for a package.
+     *
+     * <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts
+     * in the list will be ignored.
+     *
+     * <p>Unlike pinned shortcuts, where different callers can have different sets of pinned
+     * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same
+     * shortcut, it can be uncached by any valid caller.
+     *
+     * @param packageName The target package name.
+     * @param shortcutIds The IDs of the shortcut to be cached.
+     * @param user The UserHandle of the profile.
+     * @throws IllegalStateException when the user is locked, or when the {@code user} user
+     * is locked or not running.
+     *
+     * @see ShortcutManager
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+    public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+            @NonNull UserHandle user) {
+        logErrorForInvalidProfileAccess(user);
+        try {
+            mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove cached flag from shortcuts for a package.
+     *
+     * @param packageName The target package name.
+     * @param shortcutIds The IDs of the shortcut to be uncached.
+     * @param user The UserHandle of the profile.
+     * @throws IllegalStateException when the user is locked, or when the {@code user} user
+     * is locked or not running.
+     *
+     * @see ShortcutManager
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+    public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+            @NonNull UserHandle user) {
+        logErrorForInvalidProfileAccess(user);
+        try {
+            mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @hide kept for testing.
      */
     @Deprecated
@@ -1560,6 +1731,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/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 6743a3f..9735f81 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -74,6 +74,11 @@
     public boolean multiArch;
 
     /**
+     * The android:debuggable flag from the package manifest.
+     */
+    public boolean debuggable;
+
+    /**
      * Specifies the recommended install location. Can be one of
      * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
      * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
@@ -108,6 +113,7 @@
         dest.writeInt(recommendedInstallLocation);
         dest.writeInt(installLocation);
         dest.writeInt(multiArch ? 1 : 0);
+        dest.writeInt(debuggable ? 1 : 0);
 
         if (verifiers == null || verifiers.length == 0) {
             dest.writeInt(0);
@@ -139,6 +145,7 @@
         recommendedInstallLocation = source.readInt();
         installLocation = source.readInt();
         multiArch = (source.readInt() != 0);
+        debuggable = (source.readInt() != 0);
 
         final int verifiersLength = source.readInt();
         if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 51d5c3f6..e8668f1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -60,6 +60,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.util.AndroidException;
@@ -596,6 +597,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final int MODULE_APEX_NAME = 0x00000001;
 
     /** @hide */
@@ -2997,6 +2999,18 @@
     public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the requisite kernel support to support incremental delivery aka Incremental FileSystem.
+     *
+     * @see IncrementalManager#isEnabled
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_INCREMENTAL_DELIVERY =
+            "android.software.incremental_delivery";
+
+    /**
      * Extra field name for the URI to a verification file. Passed to a package
      * verifier.
      *
@@ -3354,6 +3368,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 +3434,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 +4259,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 +7397,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);
         }
     }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index e6f682d..a69905e 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,
@@ -84,4 +85,11 @@
 
     public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage,
             int callingUid);
+
+    public abstract void cacheShortcuts(int launcherUserId,
+            @NonNull String callingPackage, @NonNull String packageName,
+            @NonNull List<String> shortcutIds, int userId);
+    public abstract void uncacheShortcuts(int launcherUserId,
+            @NonNull String callingPackage, @NonNull String packageName,
+            @NonNull List<String> shortcutIds, int userId);
 }
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/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index fbe5a48..da17ff3 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -286,6 +286,8 @@
 
     List<String> getQueriesPackages();
 
+    Set<String> getQueriesProviders();
+
     String getRealPackage();
 
     // TODO(b/135203078): Rename to getRequiredFeatures? Somewhat ambiguous whether "Req" is
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 5c8c9a4..548d82a 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -96,6 +96,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 /** @hide */
 public class ApkParseUtils {
@@ -1817,6 +1818,25 @@
                     );
                 }
                 parsingPackage.addQueriesPackage(packageName.intern());
+            } else if (parser.getName().equals("provider")) {
+                final TypedArray sa = res.obtainAttributes(parser,
+                        R.styleable.AndroidManifestQueriesProvider);
+                try {
+                    final String authorities =
+                            sa.getString(R.styleable.AndroidManifestQueriesProvider_authorities);
+                    if (TextUtils.isEmpty(authorities)) {
+                        return parseInput.error(
+                                PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                                "Authority missing from provider tag."
+                        );
+                    }
+                    StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");
+                    while (authoritiesTokenizer.hasMoreElements()) {
+                        parsingPackage.addQueriesProvider(authoritiesTokenizer.nextToken());
+                    }
+                } finally {
+                    sa.recycle();
+                }
             }
         }
         return parseInput.success(parsingPackage);
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index fe8307c..0df9500 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -216,6 +216,9 @@
     private ArrayList<String> queriesPackages;
 
     @Nullable
+    private ArraySet<String> queriesProviders;
+
+    @Nullable
     private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes;
 
     private String[] splitClassLoaderNames;
@@ -957,6 +960,12 @@
     }
 
     @Override
+    public ParsingPackage addQueriesProvider(String authority) {
+        this.queriesProviders = ArrayUtils.add(this.queriesProviders, authority);
+        return this;
+    }
+
+    @Override
     public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) {
         this.processes = processes;
         return this;
@@ -2975,6 +2984,11 @@
         return queriesPackages;
     }
 
+    @Override
+    public Set<String> getQueriesProviders() {
+        return queriesProviders;
+    }
+
     private static void internStringArrayList(List<String> list) {
         if (list != null) {
             final int N = list.size();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 9ddcc09..a2fe064 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -100,6 +100,8 @@
 
     ParsingPackage addQueriesPackage(String packageName);
 
+    ParsingPackage addQueriesProvider(String authority);
+
     ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes);
 
     ParsingPackage asSplit(
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index 73b8a48..7ebeb21 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -216,6 +216,10 @@
      * across device reboot, by simulating what happens on reboot without
      * actually rebooting the device.
      *
+     * Note rollbacks in the process of enabling will be lost after calling
+     * this method since they are not persisted yet. Don't call this method
+     * in the middle of the install process.
+     *
      * @throws SecurityException if the caller does not have appropriate permissions.
      *
      * @hide
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/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 9bd3992..c1df5b6 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -221,10 +221,8 @@
     /**
      * Set a model specific {@link ModelParams} with the given value. This
      * parameter will keep its value for the duration the model is loaded regardless of starting
-     * and
-     * stopping recognition. Once the model is unloaded, the value will be lost.
-     * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
-     * this method.
+     * and stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link #queryParameter} should be checked first before calling this method.
      *
      * @param soundModelHandle handle of model to apply parameter
      * @param modelParam       {@link ModelParams}
@@ -251,22 +249,14 @@
      * for the duration the model is loaded regardless of starting and stopping recognition.
      * Once the model is unloaded, the value will be lost. If the value is not set, a default
      * value is returned. See {@link ModelParams} for parameter default values.
-     * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before
+     * {@link #queryParameter} should be checked first before
      * calling this method. Otherwise, an exception can be thrown.
      *
      * @param soundModelHandle handle of model to get parameter
      * @param modelParam       {@link ModelParams}
      * @return value of parameter
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     *                                       {@link SoundTriggerModule#queryParameter(int, int)}
-     *                                       should
-     *                                       be checked first.
-     * @throws IllegalArgumentException      if invalid model handle or parameter is passed.
-     *                                       {@link SoundTriggerModule#queryParameter(int, int)}
-     *                                       should be checked first.
      */
-    public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+    public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) {
         try {
             return mService.getModelParameter(soundModelHandle,
                     ConversionUtil.api2aidlModelParameter(modelParam));
@@ -276,9 +266,8 @@
     }
 
     /**
-     * Determine if parameter control is supported for the given model handle.
-     * This method should be checked prior to calling {@link SoundTriggerModule#setParameter} or
-     * {@link SoundTriggerModule#getParameter}.
+     * Query the parameter support and range for a given {@link ModelParams}.
+     * This method should be check prior to calling {@link #setParameter} or {@link #getParameter}.
      *
      * @param soundModelHandle handle of model to get parameter
      * @param modelParam       {@link ModelParams}
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index b128ea7..3c39d15 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -202,7 +202,7 @@
          */
         @NetworkProbe
         public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK =
-                "networkProbesAttemped";
+                "networkProbesAttempted";
 
         /** @hide */
         @StringDef(prefix = {"KEY_"}, value = {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fa12c08..a305948 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2423,14 +2423,14 @@
     /**
      * Get the set of tethered dhcp ranges.
      *
-     * @return an array of 0 or more {@code String} of tethered dhcp ranges.
-     * @deprecated This API just return the default value which is not used in DhcpServer.
+     * @deprecated This method is not supported.
+     * TODO: remove this function when all of clients are removed.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     @Deprecated
     public String[] getTetheredDhcpRanges() {
-        return getTetheringManager().getTetheredDhcpRanges();
+        throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported");
     }
 
     /**
@@ -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 0fae607..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);
 
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index cf5f225..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;
     }
 
     /**
@@ -865,9 +881,11 @@
      * @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/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 26d9c7d..7512352 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -27,6 +27,7 @@
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
 import java.nio.NioUtils;
@@ -272,6 +273,20 @@
         dest.writeFileDescriptor(mFileDescriptor);
     }
 
+    /**
+     * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor.
+     * This obeys standard POSIX semantics, where the
+     * new file descriptor shared state such as file position with the
+     * original file descriptor.
+     * TODO: propose this method as a public or system API for next release to achieve parity with
+     *  NDK ASharedMemory_dupFromJava.
+     *
+     * @hide
+     */
+    public ParcelFileDescriptor getFdDup() throws IOException {
+        return ParcelFileDescriptor.dup(mFileDescriptor);
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
             new Parcelable.Creator<SharedMemory>() {
         @Override
diff --git a/core/java/android/os/StatsServiceManager.java b/core/java/android/os/StatsServiceManager.java
index d032e98..de07e92 100644
--- a/core/java/android/os/StatsServiceManager.java
+++ b/core/java/android/os/StatsServiceManager.java
@@ -18,17 +18,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the stats
  * service.
  *
  * <p> Only the statsd mainline module will be able to access an instance of this class.
- *
- * TODO(b/148225705) Change to @SystemApi(client=MODULE_LIBRARIES) when the build system is ready.
  * @hide
  */
-@SystemApi
+@SystemApi(client = Client.MODULE_LIBRARIES)
 public class StatsServiceManager {
     /**
      * @hide
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 25584f1..3508b70 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -101,6 +101,8 @@
     public static final long TRACE_TAG_NNAPI = 1L << 25;
     /** @hide */
     public static final long TRACE_TAG_RRO = 1L << 26;
+    /** @hide */
+    public static final long TRACE_TAG_APEX_MANAGER = 1L << 18;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
     private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 6ed1d2c..b202053 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2325,10 +2325,12 @@
      * @param restrictionKey the string key representing the restriction
      * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
      */
+    @TestApi
     @UnsupportedAppUsage
-    @RequiresPermission(Manifest.permission.MANAGE_USERS)
-    public boolean hasBaseUserRestriction(@UserRestrictionKey String restrictionKey,
-            UserHandle userHandle) {
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public boolean hasBaseUserRestriction(@UserRestrictionKey @NonNull String restrictionKey,
+            @NonNull UserHandle userHandle) {
         try {
             return mService.hasBaseUserRestriction(restrictionKey, userHandle.getIdentifier());
         } catch (RemoteException re) {
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 21434a2..9d98b3b 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -103,4 +103,9 @@
      * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
      */
     void deleteStorage(int storageId);
+
+    /**
+     * Setting up native library directories and extract native libs onto a storage.
+     */
+    boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi);
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 8990bdf..ba38268 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;
@@ -193,116 +191,79 @@
     }
 
     /**
-     * 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);
         }
+        linkFiles(apkStorage, beforeCodeFile, "", linkedApkStorage, afterCodePathName);
+        apkStorage.unBind(beforeCodePath);
     }
 
-    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);
+    /**
+     * Recursively set up directories and link all the files from source storage to target storage.
+     *
+     * @param sourceStorage The storage that has all the files and directories underneath.
+     * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
+     * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
+     * @param targetStorage The target storage that will have the same files and directories.
+     * @param targetRelativePath The relative path to the directory on the target storage that
+     *                           should have all the files and dirs underneath,
+     *                           e.g., "packageName-random".
+     * @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
+     */
+    private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
+            String sourceRelativePath, IncrementalStorage targetStorage,
+            String targetRelativePath) throws IOException {
+        targetStorage.makeDirectory(targetRelativePath);
+        final File[] entryList = sourceAbsolutePath.listFiles();
+        for (int i = 0; i < entryList.length; i++) {
+            final File entry = entryList[i];
+            final String entryName = entryList[i].getName();
+            final String sourceEntryRelativePath =
+                    sourceRelativePath.isEmpty() ? entryName : sourceRelativePath + "/" + entryName;
+            final String targetEntryRelativePath = targetRelativePath + "/" + entryName;
+            if (entry.isFile()) {
+                sourceStorage.makeLink(
+                        sourceEntryRelativePath, targetStorage, targetEntryRelativePath);
+            } else if (entry.isDirectory()) {
+                linkFiles(sourceStorage, entry, sourceEntryRelativePath, targetStorage,
+                        targetEntryRelativePath);
             }
         }
-        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());
-        }
     }
 
     /**
@@ -325,6 +286,13 @@
     }
 
     /**
+     * Checks if Incremental is enabled
+     */
+    public static boolean isEnabled() {
+        return nativeIsEnabled();
+    }
+
+    /**
      * Checks if path is mounted on Incremental File System.
      */
     public static boolean isIncrementalPath(@NonNull String path) {
@@ -332,5 +300,6 @@
     }
 
     /* Native methods */
+    private static native boolean nativeIsEnabled();
     private static native boolean nativeIsIncrementalPath(@NonNull String path);
 }
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index c4b843b..5df44ff 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -416,4 +416,24 @@
             return false;
         }
     }
+
+    /**
+     * Configure all the lib files inside Incremental Service, e.g., create lib dirs, create new lib
+     * files, extract original lib file data from zip and then write data to the lib files on the
+     * Incremental File System.
+     *
+     * @param apkFullPath Source APK to extract native libs from.
+     * @param libDirRelativePath Target dir to put lib files, e.g., "lib" or "lib/arm".
+     * @param abi Target ABI of the native lib files. Only extract native libs of this ABI.
+     * @return Success of not.
+     */
+    public boolean configureNativeBinaries(String apkFullPath, String libDirRelativePath,
+            String abi) {
+        try {
+            return mService.configureNativeBinaries(mId, apkFullPath, libDirRelativePath, abi);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 30e4eee08..b9207e5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1830,6 +1830,17 @@
     public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
 
     /**
+     * Activity Extra: The {@link NotificationChannel#getConversationId()} of the notification
+     * conversation settings to display.
+     * <p>
+     * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If
+     * included the system will first look up notification settings by channel and conversation id,
+     * and will fall back to channel id if a specialized channel for this conversation doesn't
+     * exist, similar to {@link NotificationManager#getNotificationChannel(String, String)}.
+     */
+    public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
+
+    /**
      * Activity Action: Show notification redaction settings.
      *
      * @hide
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 31e77f35..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/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 707426a..0edd013 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -345,9 +345,11 @@
     }
 
     /**
-     * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
+     * Notifies the service of {@link SnapshotData snapshot data} associated with an activity.
      *
-     * @param sessionId the session's Id
+     * @param sessionId the session's Id. This may also be
+     *                  {@link ContentCaptureSession#NO_SESSION_ID} if no content capture session
+     *                  exists for the activity being snapshotted
      * @param snapshotData the data
      */
     public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/service/notification/ConversationChannelWrapper.aidl
similarity index 76%
rename from core/java/android/os/StatsDimensionsValue.aidl
rename to core/java/android/service/notification/ConversationChannelWrapper.aidl
index 81a14a4..f324158 100644
--- a/core/java/android/os/StatsDimensionsValue.aidl
+++ b/core/java/android/service/notification/ConversationChannelWrapper.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
+/*
+ * Copyright (c) 2020, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.service.notification;
 
-/** @hide */
-parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file
+parcelable ConversationChannelWrapper;
\ No newline at end of file
diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java
new file mode 100644
index 0000000..9847695
--- /dev/null
+++ b/core/java/android/service/notification/ConversationChannelWrapper.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 android.service.notification;
+
+import android.app.NotificationChannel;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class ConversationChannelWrapper implements Parcelable {
+
+    private NotificationChannel mNotificationChannel;
+    private CharSequence mGroupLabel;
+    private CharSequence mParentChannelLabel;
+    private ShortcutInfo mShortcutInfo;
+
+    public ConversationChannelWrapper() {}
+
+    protected ConversationChannelWrapper(Parcel in) {
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mGroupLabel = in.readCharSequence();
+        mParentChannelLabel = in.readCharSequence();
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mNotificationChannel, flags);
+        dest.writeCharSequence(mGroupLabel);
+        dest.writeCharSequence(mParentChannelLabel);
+        dest.writeParcelable(mShortcutInfo, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ConversationChannelWrapper> CREATOR =
+            new Creator<ConversationChannelWrapper>() {
+                @Override
+                public ConversationChannelWrapper createFromParcel(Parcel in) {
+                    return new ConversationChannelWrapper(in);
+                }
+
+                @Override
+                public ConversationChannelWrapper[] newArray(int size) {
+                    return new ConversationChannelWrapper[size];
+                }
+            };
+
+
+    public NotificationChannel getNotificationChannel() {
+        return mNotificationChannel;
+    }
+
+    public void setNotificationChannel(
+            NotificationChannel notificationChannel) {
+        mNotificationChannel = notificationChannel;
+    }
+
+    public CharSequence getGroupLabel() {
+        return mGroupLabel;
+    }
+
+    public void setGroupLabel(CharSequence groupLabel) {
+        mGroupLabel = groupLabel;
+    }
+
+    public CharSequence getParentChannelLabel() {
+        return mParentChannelLabel;
+    }
+
+    public void setParentChannelLabel(CharSequence parentChannelLabel) {
+        mParentChannelLabel = parentChannelLabel;
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
+    public void setShortcutInfo(ShortcutInfo shortcutInfo) {
+        mShortcutInfo = shortcutInfo;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ConversationChannelWrapper that = (ConversationChannelWrapper) o;
+        return Objects.equals(getNotificationChannel(), that.getNotificationChannel()) &&
+                Objects.equals(getGroupLabel(), that.getGroupLabel()) &&
+                Objects.equals(getParentChannelLabel(), that.getParentChannelLabel()) &&
+                Objects.equals(getShortcutInfo(), that.getShortcutInfo());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getNotificationChannel(), getGroupLabel(), getParentChannelLabel(),
+                getShortcutInfo());
+    }
+}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e053ed5..0ff2e03 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -214,6 +214,32 @@
     public static final int REASON_TIMEOUT = 19;
 
     /**
+     * @hide
+     */
+    @IntDef(prefix = "REASON_", value = {
+            REASON_CLICK,
+            REASON_CANCEL,
+            REASON_CANCEL_ALL,
+            REASON_ERROR,
+            REASON_PACKAGE_CHANGED,
+            REASON_USER_STOPPED,
+            REASON_PACKAGE_BANNED,
+            REASON_APP_CANCEL,
+            REASON_APP_CANCEL_ALL,
+            REASON_LISTENER_CANCEL,
+            REASON_LISTENER_CANCEL_ALL,
+            REASON_GROUP_SUMMARY_CANCELED,
+            REASON_GROUP_OPTIMIZATION,
+            REASON_PACKAGE_SUSPENDED,
+            REASON_PROFILE_TURNED_OFF,
+            REASON_UNAUTOBUNDLED,
+            REASON_CHANNEL_BANNED,
+            REASON_SNOOZED,
+            REASON_TIMEOUT
+    })
+    public @interface NotificationCancelReason{};
+
+    /**
      * The full trim of the StatusBarNotification including all its features.
      *
      * @hide
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index d0675ed..b4b5819 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -126,22 +126,22 @@
             = "android.service.quicksettings.ACTIVE_TILE";
 
     /**
-     * Meta-data for a tile to support {@code BooleanState}.
+     * Meta-data for a tile to mark is toggleable.
      * <p>
-     * BooleanState is for tiles that should support switch tile behavior in accessibility. This is
+     * Toggleable tiles support switch tile behavior in accessibility. This is
      * the behavior of most of the framework tiles.
      *
-     * To make a TileService support BooleanState, set this meta-data to true on the TileService's
-     * manifest declaration.
+     * To indicate that a TileService is toggleable, set this meta-data to true on the
+     * TileService's manifest declaration.
      * <pre class="prettyprint">
      * {@literal
-     * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+     * <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
      *      android:value="true" />
      * }
      * </pre>
      */
-    public static final String META_DATA_BOOLEAN_TILE =
-            "android.service.quicksettings.BOOLEAN_TILE";
+    public static final String META_DATA_TOGGLEABLE_TILE =
+            "android.service.quicksettings.TOGGLEABLE_TILE";
 
     /**
      * Used to notify SysUI that Listening has be requested.
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 848868a..3ff6f54 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -41,6 +41,7 @@
 import android.view.textclassifier.ConversationActions;
 import android.view.textclassifier.SelectionEvent;
 import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationConstants;
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassificationSessionId;
@@ -404,22 +405,27 @@
      */
     @NonNull
     public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
-        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.");
-        }
         final TextClassificationManager tcm =
                 context.getSystemService(TextClassificationManager.class);
-        if (tcm != null) {
-            return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE);
+        if (tcm == null) {
+            return TextClassifier.NO_OP;
         }
-        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);
+        }
     }
 
     /** @hide **/
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/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 531ade0..e65bd9f 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -412,23 +412,23 @@
      * domain. This indication does not necessarily indicate a change of service state, which should
      * be tracked via {@link #LISTEN_SERVICE_STATE}.
      *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
-     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
+     * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onRegistrationFailed()
      */
-    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
 
     /**
      * Listen for Barring Information for the current registered / camped cell.
      *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
-     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
+     * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onBarringInfoChanged()
      */
-    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public static final int LISTEN_BARRING_INFO = 0x80000000;
 
     /*
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
index ab2c4fc..ee3a8a7 100644
--- a/core/java/android/timezone/CountryTimeZones.java
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.icu.util.TimeZone;
 
 import java.util.ArrayList;
@@ -206,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/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/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 6463509..1d3e6d0 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -66,46 +66,48 @@
     public static PackageParser.SigningDetails verify(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion)
             throws PackageParserException {
+        return verifySignatures(apkPath, minSignatureSchemeVersion, true);
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.
+     *
+     * @throws PackageParserException if there was a problem collecting certificates.
+     */
+    public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification(
+            String apkPath, int minSignatureSchemeVersion)
+            throws PackageParserException {
+        return verifySignatures(apkPath, minSignatureSchemeVersion, false);
+    }
+
+    /**
+     * Verifies the provided APK using all allowed signing schemas.
+     * @return the certificates associated with each signer.
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @throws PackageParserException if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifySignatures(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+            throws PackageParserException {
 
         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
             // V3 and before are older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
-            + " or newer for package " + apkPath);
+                            + " or newer for package " + apkPath);
         }
 
         // first try v3
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
         try {
-            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
-                    ApkSignatureSchemeV3Verifier.verify(apkPath);
-            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
-            Signature[] signerSigs = convertToSignatures(signerCerts);
-            Signature[] pastSignerSigs = null;
-            if (vSigner.por != null) {
-                // populate proof-of-rotation information
-                pastSignerSigs = new Signature[vSigner.por.certs.size()];
-                for (int i = 0; i < pastSignerSigs.length; i++) {
-                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
-                    pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
-                }
-            }
-            return new PackageParser.SigningDetails(
-                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
-                    pastSignerSigs);
+            return verifyV3Signature(apkPath, verifyFull);
         } catch (SignatureNotFoundException e) {
             // not signed with v3, try older if allowed
             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
             }
-        } catch (Exception e) {
-            // APK Signature Scheme v2 signature found but did not verify
-            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "Failed to collect certificates from " + apkPath
-                            + " using APK Signature Scheme v3", e);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
         // redundant, protective version check
@@ -117,26 +119,14 @@
         }
 
         // try v2
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
         try {
-            Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
-            Signature[] signerSigs = convertToSignatures(signerCerts);
-
-            return new PackageParser.SigningDetails(
-                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2);
+            return verifyV2Signature(apkPath, verifyFull);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
             }
-        } catch (Exception e) {
-            // APK Signature Scheme v2 signature found but did not verify
-            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "Failed to collect certificates from " + apkPath
-                            + " using APK Signature Scheme v2", e);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
         // redundant, protective version check
@@ -148,14 +138,83 @@
         }
 
         // v2 didn't work, try jarsigner
-        return verifyV1Signature(apkPath, true);
+        return verifyV1Signature(apkPath, verifyFull);
     }
 
     /**
-     * Verifies the provided APK and returns the certificates associated with each signer.
+     * Verifies the provided APK using V3 schema.
      *
      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @return the certificates associated with each signer.
+     * @throws SignatureNotFoundException if there are no V3 signatures in the APK
+     * @throws PackageParserException     if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV3Signature(String apkPath,
+            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath)
+                            : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(
+                                    apkPath);
+            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            Signature[] pastSignerSigs = null;
+            if (vSigner.por != null) {
+                // populate proof-of-rotation information
+                pastSignerSigs = new Signature[vSigner.por.certs.size()];
+                for (int i = 0; i < pastSignerSigs.length; i++) {
+                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
+                    pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
+                }
+            }
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs);
+        } catch (SignatureNotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            // APK Signature Scheme v3 signature found but did not verify
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v3", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Verifies the provided APK using V2 schema.
      *
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @return the certificates associated with each signer.
+     * @throws SignatureNotFoundException if there are no V2 signatures in the APK
+     * @throws PackageParserException     if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV2Signature(String apkPath,
+            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
+        try {
+            Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath)
+                    : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V2);
+        } catch (SignatureNotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Verifies the provided APK using JAR schema.
+     * @return the certificates associated with each signer.
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
      * @throws PackageParserException if there was a problem collecting certificates
      */
     private static PackageParser.SigningDetails verifyV1Signature(
@@ -277,7 +336,7 @@
      *
      * @throws CertificateEncodingException if it is unable to create a Signature object.
      */
-    public static Signature[] convertToSignatures(Certificate[][] certs)
+    private static Signature[] convertToSignatures(Certificate[][] certs)
             throws CertificateEncodingException {
         final Signature[] res = new Signature[certs.length];
         for (int i = 0; i < certs.length; i++) {
@@ -296,99 +355,29 @@
     }
 
     /**
-     * Returns the certificates associated with each signer for the given APK without verification.
-     * This method is dangerous and should not be used, unless the caller is absolutely certain the
-     * APK is trusted.
-     *
-     * @throws PackageParserException if the APK's signature failed to verify.
-     * or greater is not found, except in the case of no JAR signature.
+     * Returns the minimum signature scheme version required for an app targeting the specified
+     * {@code targetSdk}.
      */
-    public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification(
-            String apkPath, int minSignatureSchemeVersion)
-            throws PackageParserException {
-
-        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
-            // V3 and before are older than the requested minimum signing version
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "No signature found in package of version " + minSignatureSchemeVersion
-                            + " or newer for package " + apkPath);
+    public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
+        if (targetSdk >= Build.VERSION_CODES.R) {
+            return SignatureSchemeVersion.SIGNING_BLOCK_V2;
         }
+        return SignatureSchemeVersion.JAR;
+    }
 
-        // first try v3
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3");
-        try {
-            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
-                    ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
-            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
-            Signature[] signerSigs = convertToSignatures(signerCerts);
-            Signature[] pastSignerSigs = null;
-            if (vSigner.por != null) {
-                // populate proof-of-rotation information
-                pastSignerSigs = new Signature[vSigner.por.certs.size()];
-                for (int i = 0; i < pastSignerSigs.length; i++) {
-                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
-                    pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
-                }
-            }
-            return new PackageParser.SigningDetails(
-                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
-                    pastSignerSigs);
-        } catch (SignatureNotFoundException e) {
-            // not signed with v3, try older if allowed
-            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
-            }
-        } catch (Exception e) {
-            // APK Signature Scheme v3 signature found but did not verify
-            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "Failed to collect certificates from " + apkPath
-                            + " using APK Signature Scheme v3", e);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    /**
+     * Result of a successful APK verification operation.
+     */
+    public static class Result {
+        public final Certificate[][] certs;
+        public final Signature[] sigs;
+        public final int signatureSchemeVersion;
+
+        public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
+            this.certs = certs;
+            this.sigs = sigs;
+            this.signatureSchemeVersion = signingVersion;
         }
-
-        // redundant, protective version check
-        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
-            // V2 and before are older than the requested minimum signing version
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "No signature found in package of version " + minSignatureSchemeVersion
-                            + " or newer for package " + apkPath);
-        }
-
-        // first try v2
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
-        try {
-            Certificate[][] signerCerts =
-                    ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
-            Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new PackageParser.SigningDetails(signerSigs,
-                    SignatureSchemeVersion.SIGNING_BLOCK_V2);
-        } catch (SignatureNotFoundException e) {
-            // not signed with v2, try older if allowed
-            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                        "No APK Signature Scheme v2 signature in package " + apkPath, e);
-            }
-        } catch (Exception e) {
-            // APK Signature Scheme v2 signature found but did not verify
-            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "Failed to collect certificates from " + apkPath
-                            + " using APK Signature Scheme v2", e);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-
-        // redundant, protective version check
-        if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
-            // V1 and is older than the requested minimum signing version
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "No signature found in package of version " + minSignatureSchemeVersion
-                            + " or newer for package " + apkPath);
-        }
-
-        // v2 didn't work, try jarsigner
-        return verifyV1Signature(apkPath, false);
     }
 
     /**
@@ -416,7 +405,7 @@
      */
     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
-                   NoSuchAlgorithmException {
+            NoSuchAlgorithmException {
         // first try v3
         try {
             return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
@@ -446,30 +435,4 @@
             return null;
         }
     }
-
-    /**
-     * Returns the minimum signature scheme version required for an app targeting the specified
-     * {@code targetSdk}.
-     */
-    public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
-        if (targetSdk >= Build.VERSION_CODES.R) {
-            return SignatureSchemeVersion.SIGNING_BLOCK_V2;
-        }
-        return SignatureSchemeVersion.JAR;
-    }
-
-    /**
-     * Result of a successful APK verification operation.
-     */
-    public static class Result {
-        public final Certificate[][] certs;
-        public final Signature[] sigs;
-        public final int signatureSchemeVersion;
-
-        public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
-            this.certs = certs;
-            this.sigs = sigs;
-            this.signatureSchemeVersion = signingVersion;
-        }
-    }
 }
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
new file mode 100644
index 0000000..d21a952
--- /dev/null
+++ b/core/java/android/view/CutoutSpecification.java
@@ -0,0 +1,486 @@
+/*
+ * 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.view;
+
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.RIGHT;
+import static android.view.Gravity.TOP;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * In order to accept the cutout specification for all of edges in devices, the specification
+ * parsing method is extracted from
+ * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be
+ * the specified class for parsing the specification.
+ * BNF definition:
+ * <ul>
+ *      <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li>
+ *      <li>Cutout Specification  = [Vertical Position], (SVG Path Element), [Horizontal Position]
+ *                                  [Bind Cutout] ;</li>
+ *      <li>Vertical Position     = "@bottom" | "@center_vertical" ;</li>
+ *      <li>Horizontal Position   = "@left" | "@right" ;</li>
+ *      <li>Bind Cutout           = "@bind_left_cutout" | "@bind_right_cutout" ;</li>
+ *      <li>Cutout Delimiter      = "@cutout" ;</li>
+ *      <li>Dp                    = "@dp"</li>
+ * </ul>
+ *
+ * <ul>
+ *     <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical"
+ *     </li>
+ *     <li>Horizontal position is center horizontal by default if there is neither "@left" nor
+ *     "@right".</li>
+ *     <li>@bottom make the cutout piece bind to bottom edge.</li>
+ *     <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to
+ *     left or right edge cutout.</li>
+ * </ul>
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public class CutoutSpecification {
+    private static final String TAG = "CutoutSpecification";
+    private static final boolean DEBUG = false;
+
+    private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length();
+
+    private static final char MARKER_START_CHAR = '@';
+    private static final String DP_MARKER = MARKER_START_CHAR + "dp";
+
+    private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom";
+    private static final String RIGHT_MARKER = MARKER_START_CHAR + "right";
+    private static final String LEFT_MARKER = MARKER_START_CHAR + "left";
+    private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout";
+    private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical";
+
+    /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */
+    private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout";
+    private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout";
+
+    private final Path mPath;
+    private final Rect mLeftBound;
+    private final Rect mTopBound;
+    private final Rect mRightBound;
+    private final Rect mBottomBound;
+    private final Insets mInsets;
+
+    private CutoutSpecification(@NonNull Parser parser) {
+        mPath = parser.mPath;
+        mLeftBound = parser.mLeftBound;
+        mTopBound = parser.mTopBound;
+        mRightBound = parser.mRightBound;
+        mBottomBound = parser.mBottomBound;
+        mInsets = parser.mInsets;
+
+        if (DEBUG) {
+            Log.d(TAG, String.format(Locale.ENGLISH,
+                    "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s",
+                    mLeftBound != null ? mLeftBound.toString() : "",
+                    mTopBound != null ? mTopBound.toString() : "",
+                    mRightBound != null ? mRightBound.toString() : "",
+                    mBottomBound != null ? mBottomBound.toString() : ""));
+        }
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Path getPath() {
+        return mPath;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getLeftBound() {
+        return mLeftBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getTopBound() {
+        return mTopBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getRightBound() {
+        return mRightBound;
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public Rect getBottomBound() {
+        return mBottomBound;
+    }
+
+    /**
+     * To count the safe inset according to the cutout bounds and waterfall inset.
+     *
+     * @return the safe inset.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    @NonNull
+    public Rect getSafeInset() {
+        return mInsets.toRect();
+    }
+
+    private static int decideWhichEdge(boolean isTopEdgeShortEdge,
+            boolean isShortEdge, boolean isStart) {
+        return (isTopEdgeShortEdge)
+                ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT))
+                : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM));
+    }
+
+    /**
+     * The CutoutSpecification Parser.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    public static class Parser {
+        private final boolean mIsShortEdgeOnTop;
+        private final float mDensity;
+        private final int mDisplayWidth;
+        private final int mDisplayHeight;
+        private final Matrix mMatrix;
+        private Insets mInsets;
+        private int mSafeInsetLeft;
+        private int mSafeInsetTop;
+        private int mSafeInsetRight;
+        private int mSafeInsetBottom;
+
+        private final Rect mTmpRect = new Rect();
+        private final RectF mTmpRectF = new RectF();
+
+        private boolean mInDp;
+
+        private Path mPath;
+        private Rect mLeftBound;
+        private Rect mTopBound;
+        private Rect mRightBound;
+        private Rect mBottomBound;
+
+        private boolean mPositionFromLeft = false;
+        private boolean mPositionFromRight = false;
+        private boolean mPositionFromBottom = false;
+        private boolean mPositionFromCenterVertical = false;
+
+        private boolean mBindLeftCutout = false;
+        private boolean mBindRightCutout = false;
+        private boolean mBindBottomCutout = false;
+
+        private boolean mIsTouchShortEdgeStart;
+        private boolean mIsTouchShortEdgeEnd;
+        private boolean mIsCloserToStartSide;
+
+        /**
+         * The constructor of the CutoutSpecification parser to parse the specification of cutout.
+         * @param density the display density.
+         * @param displayWidth the display width.
+         * @param displayHeight the display height.
+         */
+        @VisibleForTesting(visibility = PACKAGE)
+        public Parser(float density, int displayWidth, int displayHeight) {
+            mDensity = density;
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            mMatrix = new Matrix();
+            mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight;
+        }
+
+        private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+            mTmpRectF.setEmpty();
+            p.computeBounds(mTmpRectF, false /* unused */);
+            mTmpRectF.round(inoutRect);
+            inoutRegion.op(inoutRect, Region.Op.UNION);
+        }
+
+        private void resetStatus(StringBuilder sb) {
+            sb.setLength(0);
+            mPositionFromBottom = false;
+            mPositionFromLeft = false;
+            mPositionFromRight = false;
+            mPositionFromCenterVertical = false;
+
+            mBindLeftCutout = false;
+            mBindRightCutout = false;
+            mBindBottomCutout = false;
+        }
+
+        private void translateMatrix() {
+            final float offsetX;
+            if (mPositionFromRight) {
+                offsetX = mDisplayWidth;
+            } else if (mPositionFromLeft) {
+                offsetX = 0;
+            } else {
+                offsetX = mDisplayWidth / 2f;
+            }
+
+            final float offsetY;
+            if (mPositionFromBottom) {
+                offsetY = mDisplayHeight;
+            } else if (mPositionFromCenterVertical) {
+                offsetY = mDisplayHeight / 2f;
+            } else {
+                offsetY = 0;
+            }
+
+            mMatrix.reset();
+            if (mInDp) {
+                mMatrix.postScale(mDensity, mDensity);
+            }
+            mMatrix.postTranslate(offsetX, offsetY);
+        }
+
+        private int computeSafeInsets(int gravity, Rect rect) {
+            if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) {
+                return rect.right;
+            } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) {
+                return rect.bottom;
+            } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) {
+                return mDisplayWidth - rect.left;
+            } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) {
+                return mDisplayHeight - rect.top;
+            }
+            return 0;
+        }
+
+        private void setSafeInset(int gravity, int inset) {
+            if (gravity == LEFT) {
+                mSafeInsetLeft = inset;
+            } else if (gravity == TOP) {
+                mSafeInsetTop = inset;
+            } else if (gravity == RIGHT) {
+                mSafeInsetRight = inset;
+            } else if (gravity == BOTTOM) {
+                mSafeInsetBottom = inset;
+            }
+        }
+
+        private int getSafeInset(int gravity) {
+            if (gravity == LEFT) {
+                return mSafeInsetLeft;
+            } else if (gravity == TOP) {
+                return mSafeInsetTop;
+            } else if (gravity == RIGHT) {
+                return mSafeInsetRight;
+            } else if (gravity == BOTTOM) {
+                return mSafeInsetBottom;
+            }
+            return 0;
+        }
+
+        @NonNull
+        private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) {
+            final int gravity;
+            if (isShortEdge) {
+                gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart);
+            } else {
+                if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart);
+                } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, true,
+                            mIsCloserToStartSide);
+                } else {
+                    gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart);
+                }
+            }
+
+            int oldSafeInset = getSafeInset(gravity);
+            int newSafeInset = computeSafeInsets(gravity, rect);
+            if (oldSafeInset < newSafeInset) {
+                setSafeInset(gravity, newSafeInset);
+            }
+
+            return new Rect(rect);
+        }
+
+        private void setEdgeCutout(@NonNull Path newPath) {
+            if (mBindRightCutout && mRightBound == null) {
+                mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect);
+            } else if (mBindLeftCutout && mLeftBound == null) {
+                mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect);
+            } else if (mBindBottomCutout && mBottomBound == null) {
+                mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect);
+            } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout)
+                    && mTopBound == null) {
+                mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect);
+            } else {
+                return;
+            }
+
+            if (mPath != null) {
+                mPath.addPath(newPath);
+            } else {
+                mPath = newPath;
+            }
+        }
+
+        private void parseSvgPathSpec(Region region, String spec) {
+            if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) {
+                Log.e(TAG, "According to SVG definition, it shouldn't happen");
+                return;
+            }
+            spec.trim();
+            translateMatrix();
+
+            final Path newPath = PathParser.createPathFromPathData(spec);
+            newPath.transform(mMatrix);
+            computeBoundsRectAndAddToRegion(newPath, region, mTmpRect);
+
+            if (DEBUG) {
+                Log.d(TAG, String.format(Locale.ENGLISH,
+                        "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b",
+                        mPositionFromLeft, mPositionFromRight, mPositionFromBottom,
+                        mPositionFromCenterVertical));
+                Log.d(TAG, "region = " + region);
+                Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath);
+            }
+
+            if (mTmpRect.isEmpty()) {
+                return;
+            }
+
+            if (mIsShortEdgeOnTop) {
+                mIsTouchShortEdgeStart = mTmpRect.top <= 0;
+                mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight;
+                mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2;
+            } else {
+                mIsTouchShortEdgeStart = mTmpRect.left <= 0;
+                mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth;
+                mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2;
+            }
+
+            setEdgeCutout(newPath);
+        }
+
+        private void parseSpecWithoutDp(@NonNull String specWithoutDp) {
+            Region region = Region.obtain();
+            StringBuilder sb = null;
+            int currentIndex = 0;
+            int lastIndex = 0;
+            while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) {
+                if (sb == null) {
+                    sb = new StringBuilder(specWithoutDp.length());
+                }
+                sb.append(specWithoutDp, lastIndex, currentIndex);
+
+                if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) {
+                    if (!mPositionFromRight) {
+                        mPositionFromLeft = true;
+                    }
+                    currentIndex += LEFT_MARKER.length();
+                } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) {
+                    if (!mPositionFromLeft) {
+                        mPositionFromRight = true;
+                    }
+                    currentIndex += RIGHT_MARKER.length();
+                } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) {
+                    if (!mPositionFromCenterVertical) {
+                        parseSvgPathSpec(region, sb.toString());
+                    }
+                    currentIndex += BOTTOM_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                    mBindBottomCutout = true;
+                    mPositionFromBottom = true;
+                } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) {
+                    if (!mPositionFromBottom) {
+                        parseSvgPathSpec(region, sb.toString());
+                    }
+                    currentIndex += CENTER_VERTICAL_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                    mPositionFromCenterVertical = true;
+                } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) {
+                    parseSvgPathSpec(region, sb.toString());
+                    currentIndex += CUTOUT_MARKER.length();
+
+                    /* prepare to parse the rest path */
+                    resetStatus(sb);
+                } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) {
+                    if (!mBindBottomCutout && !mBindRightCutout) {
+                        mBindLeftCutout = true;
+                    }
+                    currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
+                } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
+                    if (!mBindBottomCutout && !mBindLeftCutout) {
+                        mBindRightCutout = true;
+                    }
+                    currentIndex += BIND_RIGHT_CUTOUT_MARKER.length();
+                } else {
+                    currentIndex += 1;
+                }
+
+                lastIndex = currentIndex;
+            }
+
+            if (sb == null) {
+                parseSvgPathSpec(region, specWithoutDp);
+            } else {
+                sb.append(specWithoutDp, lastIndex, specWithoutDp.length());
+                parseSvgPathSpec(region, sb.toString());
+            }
+
+            region.recycle();
+        }
+
+        /**
+         * To parse specification string as the CutoutSpecification.
+         *
+         * @param originalSpec the specification string
+         * @return the CutoutSpecification instance
+         */
+        @VisibleForTesting(visibility = PACKAGE)
+        public CutoutSpecification parse(@NonNull String originalSpec) {
+            Objects.requireNonNull(originalSpec);
+
+            int dpIndex = originalSpec.lastIndexOf(DP_MARKER);
+            mInDp = (dpIndex != -1);
+            final String spec;
+            if (dpIndex != -1) {
+                spec = originalSpec.substring(0, dpIndex)
+                        + originalSpec.substring(dpIndex + DP_MARKER.length());
+            } else {
+                spec = originalSpec;
+            }
+
+            parseSpecWithoutDp(spec);
+
+            mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom);
+            return new CutoutSpecification(this);
+        }
+    }
+}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index d433591..31fc161 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -31,18 +31,12 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.graphics.Insets;
-import android.graphics.Matrix;
 import android.graphics.Path;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Region.Op;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
-import android.util.PathParser;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
@@ -63,10 +57,6 @@
 public final class DisplayCutout {
 
     private static final String TAG = "DisplayCutout";
-    private static final String BOTTOM_MARKER = "@bottom";
-    private static final String DP_MARKER = "@dp";
-    private static final String RIGHT_MARKER = "@right";
-    private static final String LEFT_MARKER = "@left";
 
     /**
      * Category for overlays that allow emulating a display cutout on devices that don't have
@@ -703,77 +693,16 @@
             }
         }
 
-        Path p = null;
-        Rect boundTop = null;
-        Rect boundBottom = null;
-        Rect safeInset = new Rect();
-        String bottomSpec = null;
-        if (!TextUtils.isEmpty(spec)) {
-            spec = spec.trim();
-            final float offsetX;
-            if (spec.endsWith(RIGHT_MARKER)) {
-                offsetX = displayWidth;
-                spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
-            } else if (spec.endsWith(LEFT_MARKER)) {
-                offsetX = 0;
-                spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
-            } else {
-                offsetX = displayWidth / 2f;
-            }
-            final boolean inDp = spec.endsWith(DP_MARKER);
-            if (inDp) {
-                spec = spec.substring(0, spec.length() - DP_MARKER.length());
-            }
+        spec = spec.trim();
 
-            if (spec.contains(BOTTOM_MARKER)) {
-                String[] splits = spec.split(BOTTOM_MARKER, 2);
-                spec = splits[0].trim();
-                bottomSpec = splits[1].trim();
-            }
+        CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density,
+                displayWidth, displayHeight).parse(spec);
+        Rect safeInset = cutoutSpec.getSafeInset();
+        final Rect boundLeft = cutoutSpec.getLeftBound();
+        final Rect boundTop = cutoutSpec.getTopBound();
+        final Rect boundRight = cutoutSpec.getRightBound();
+        final Rect boundBottom = cutoutSpec.getBottomBound();
 
-            final Matrix m = new Matrix();
-            final Region r = Region.obtain();
-            if (!spec.isEmpty()) {
-                try {
-                    p = PathParser.createPathFromPathData(spec);
-                } catch (Throwable e) {
-                    Log.wtf(TAG, "Could not inflate cutout: ", e);
-                }
-
-                if (p != null) {
-                    if (inDp) {
-                        m.postScale(density, density);
-                    }
-                    m.postTranslate(offsetX, 0);
-                    p.transform(m);
-
-                    boundTop = new Rect();
-                    toRectAndAddToRegion(p, r, boundTop);
-                    safeInset.top = boundTop.bottom;
-                }
-            }
-
-            if (bottomSpec != null) {
-                int bottomInset = 0;
-                Path bottomPath = null;
-                try {
-                    bottomPath = PathParser.createPathFromPathData(bottomSpec);
-                } catch (Throwable e) {
-                    Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
-                }
-
-                if (bottomPath != null) {
-                    // Keep top transform
-                    m.postTranslate(0, displayHeight);
-                    bottomPath.transform(m);
-                    p.addPath(bottomPath);
-                    boundBottom = new Rect();
-                    toRectAndAddToRegion(bottomPath, r, boundBottom);
-                    bottomInset = displayHeight - boundBottom.top;
-                }
-                safeInset.bottom = bottomInset;
-            }
-        }
 
         if (!waterfallInsets.equals(Insets.NONE)) {
             safeInset.set(
@@ -784,9 +713,9 @@
         }
 
         final DisplayCutout cutout = new DisplayCutout(
-                safeInset, waterfallInsets, null /* boundLeft */, boundTop,
-                null /* boundRight */, boundBottom, false /* copyArguments */);
-        final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
+                safeInset, waterfallInsets, boundLeft, boundTop,
+                boundRight, boundBottom, false /* copyArguments */);
+        final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
         synchronized (CACHE_LOCK) {
             sCachedSpec = spec;
             sCachedDisplayWidth = displayWidth;
@@ -798,14 +727,6 @@
         return result;
     }
 
-    private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
-        final RectF rectF = new RectF();
-        p.computeBounds(rectF, false /* unused */);
-        rectF.round(inoutRect);
-        inoutRegion.op(inoutRect, Op.UNION);
-    }
-
-
     private static Insets loadWaterfallInset(Resources res) {
         return Insets.of(
                 res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
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/os/StatsDimensionsValue.aidl b/core/java/android/view/SurfaceControlViewHost.aidl
similarity index 76%
copy from core/java/android/os/StatsDimensionsValue.aidl
copy to core/java/android/view/SurfaceControlViewHost.aidl
index 81a14a4..3b31ab8 100644
--- a/core/java/android/os/StatsDimensionsValue.aidl
+++ b/core/java/android/view/SurfaceControlViewHost.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018, The Android Open Source Project
+ * Copyright (c) 2020, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.view;
 
-/** @hide */
-parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file
+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/View.java b/core/java/android/view/View.java
index a407bd8..a6f8fad 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12729,12 +12729,14 @@
                 return findViewInsideOutShouldExist(root, mNextFocusForwardId);
             case FOCUS_BACKWARD: {
                 if (mID == View.NO_ID) return null;
-                return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
-                    @Override
-                    public boolean test(View t) {
-                        return t.findViewById(t.mNextFocusForwardId) == View.this;
-                    }
-                });
+                final View rootView = root;
+                final View startView = this;
+                // Since we have forward links but no backward links, we need to find the view that
+                // forward links to this view. We can't just find the view with the specified ID
+                // because view IDs need not be unique throughout the tree.
+                return root.findViewByPredicateInsideOut(startView,
+                    t -> findViewInsideOutShouldExist(rootView, t, t.mNextFocusForwardId)
+                            == startView);
             }
         }
         return null;
@@ -12764,11 +12766,15 @@
     }
 
     private View findViewInsideOutShouldExist(View root, int id) {
+        return findViewInsideOutShouldExist(root, this, id);
+    }
+
+    private View findViewInsideOutShouldExist(View root, View start, int id) {
         if (mMatchIdPredicate == null) {
             mMatchIdPredicate = new MatchIdPredicate();
         }
         mMatchIdPredicate.mId = id;
-        View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
+        View result = root.findViewByPredicateInsideOut(start, mMatchIdPredicate);
         if (result == null) {
             Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
         }
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 33f21f2..cf34b0b 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -25,6 +27,8 @@
 import android.os.Parcelable;
 import android.util.ArrayMap;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -36,10 +40,14 @@
 public class WindowContainerTransaction implements Parcelable {
     private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
 
+    // Flat list because re-order operations are order-dependent
+    private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
+
     public WindowContainerTransaction() {}
 
     protected WindowContainerTransaction(Parcel in) {
         in.readMap(mChanges, null /* loader */);
+        in.readList(mHierarchyOps, null /* loader */);
     }
 
     private Change getOrCreateChange(IBinder token) {
@@ -97,10 +105,39 @@
         return this;
     }
 
+    /**
+     * Reparents a container into another one. The effect of a {@code null} parent can vary. For
+     * example, reparenting a stack to {@code null} will reparent it to its display.
+     *
+     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+     *              the bottom.
+     */
+    public WindowContainerTransaction reparent(@NonNull IWindowContainer child,
+            @Nullable IWindowContainer parent, boolean onTop) {
+        mHierarchyOps.add(new HierarchyOp(child.asBinder(),
+                parent == null ? null : parent.asBinder(), onTop));
+        return this;
+    }
+
+    /**
+     * Reorders a container within its parent.
+     *
+     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+     *              the bottom.
+     */
+    public WindowContainerTransaction reorder(@NonNull IWindowContainer child, boolean onTop) {
+        mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
+        return this;
+    }
+
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
 
+    public List<HierarchyOp> getHierarchyOps() {
+        return mHierarchyOps;
+    }
+
     @Override
     public String toString() {
         return "WindowContainerTransaction { changes = " + mChanges + " }";
@@ -109,6 +146,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeMap(mChanges);
+        dest.writeList(mHierarchyOps);
     }
 
     @Override
@@ -249,4 +287,88 @@
             }
         };
     }
+
+    /**
+     * Holds information about a reparent/reorder operation in the hierarchy. This is separate from
+     * Changes because they must be executed in the same order that they are added.
+     */
+    public static class HierarchyOp implements Parcelable {
+        private final IBinder mContainer;
+
+        // If this is same as mContainer, then only change position, don't reparent.
+        private final IBinder mReparent;
+
+        // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
+        private final boolean mToTop;
+
+        public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
+            mContainer = container;
+            mReparent = reparent;
+            mToTop = toTop;
+        }
+
+        public HierarchyOp(@NonNull IBinder container, boolean toTop) {
+            mContainer = container;
+            mReparent = container;
+            mToTop = toTop;
+        }
+
+        protected HierarchyOp(Parcel in) {
+            mContainer = in.readStrongBinder();
+            mReparent = in.readStrongBinder();
+            mToTop = in.readBoolean();
+        }
+
+        public boolean isReparent() {
+            return mContainer != mReparent;
+        }
+
+        @Nullable
+        public IBinder getNewParent() {
+            return mReparent;
+        }
+
+        @NonNull
+        public IBinder getContainer() {
+            return mContainer;
+        }
+
+        public boolean getToTop() {
+            return mToTop;
+        }
+
+        @Override
+        public String toString() {
+            if (isReparent()) {
+                return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+                        + mReparent + "}";
+            } else {
+                return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+            }
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeStrongBinder(mContainer);
+            dest.writeStrongBinder(mReparent);
+            dest.writeBoolean(mToTop);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<HierarchyOp> CREATOR = new Creator<HierarchyOp>() {
+            @Override
+            public HierarchyOp createFromParcel(Parcel in) {
+                return new HierarchyOp(in);
+            }
+
+            @Override
+            public HierarchyOp[] newArray(int size) {
+                return new HierarchyOp[size];
+            }
+        };
+    }
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 4365d1f..56683dd 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -277,7 +277,7 @@
                     .setStableInsets(Insets.of(stableInsets))
                     .setDisplayCutout(displayCutout.get()).build();
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        return null;
     }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 05cf3ed..bf2de14 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -4854,8 +4854,11 @@
                 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.
+         * Action to send an ime action which is from
+         * {@link android.view.inputmethod.EditorInfo#actionId}. This action would be
+         * {@link android.view.inputmethod.EditorInfo#IME_ACTION_UNSPECIFIED} if no specific
+         * actionId defined. 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);
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 232d96b..2134dab 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.util.DebugUtils;
 import android.util.Log;
 import android.view.View;
@@ -50,7 +51,11 @@
 
     private static final Random sIdGenerator = new Random();
 
-    /** @hide */
+    /**
+    *  ID used to indicate that a session does not exist
+    *  @hide
+    */
+    @SystemApi
     public static final int NO_SESSION_ID = 0;
 
     /**
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c80a1ae..7f90d57 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -33,11 +33,11 @@
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * An EditorInfo describes several attributes of a text editing object
@@ -558,7 +558,7 @@
      *                      editor wants to trim out the first 10 chars, subTextStart should be 10.
      */
     public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
-        Preconditions.checkNotNull(subText);
+        Objects.requireNonNull(subText);
 
         // Swap selection start and end if necessary.
         final int subTextSelStart = initialSelStart > initialSelEnd
@@ -585,25 +585,35 @@
             return;
         }
 
-        // The input text is too long. Let's try to trim it reasonably. Fundamental rules are:
-        // 1. Text before the cursor is the most important information to IMEs.
-        // 2. Text after the cursor is the second important information to IMEs.
-        // 3. Selected text is the least important information but it shall NEVER be truncated.
-        //    When it is too long, just drop it.
-        //
-        // Source: <TextBeforeCursor><Selection><TextAfterCursor>
-        // Possible results:
-        // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)>
-        // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)>
-        //
-        final int sourceSelLength = subTextSelEnd - subTextSelStart;
+        trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd);
+    }
+
+    /**
+     * Trims the initial surrounding text when it is over sized. Fundamental trimming rules are:
+     * - The text before the cursor is the most important information to IMEs.
+     * - The text after the cursor is the second important information to IMEs.
+     * - The selected text is the least important information but it shall NEVER be truncated. When
+     *    it is too long, just drop it.
+     *<p><pre>
+     * For example, the subText can be viewed as
+     *     TextBeforeCursor + Selection + TextAfterCursor
+     * The result could be
+     *     1. (maybeTrimmedAtHead)TextBeforeCursor + Selection + TextAfterCursor(maybeTrimmedAtTail)
+     *     2. (maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</pre>
+     *
+     * @param subText The long text that needs to be trimmed.
+     * @param selStart The text offset of the start of the selection.
+     * @param selEnd The text offset of the end of the selection
+     */
+    private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd) {
+        final int sourceSelLength = selEnd - selStart;
         // When the selected text is too long, drop it.
         final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH)
                 ? 0 : sourceSelLength;
 
         // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio.
-        final int subTextBeforeCursorLength = subTextSelStart;
-        final int subTextAfterCursorLength = subTextLength - subTextSelEnd;
+        final int subTextBeforeCursorLength = selStart;
+        final int subTextAfterCursorLength = subText.length() - selEnd;
         final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength;
         final int possibleMaxBeforeCursorLength =
                 Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection));
@@ -617,24 +627,23 @@
 
         // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail.
         if (isCutOnSurrogate(subText,
-                subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
+                selStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
             newBeforeCursorHead = newBeforeCursorHead + 1;
             newBeforeCursorLength = newBeforeCursorLength - 1;
         }
         if (isCutOnSurrogate(subText,
-                subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
+                selEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
             newAfterCursorLength = newAfterCursorLength - 1;
         }
 
         // Now we know where to trim, compose the initialSurroundingText.
         final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength;
-        CharSequence newInitialSurroundingText;
+        final CharSequence newInitialSurroundingText;
         if (newSelLength != sourceSelLength) {
             final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead,
                     newBeforeCursorHead + newBeforeCursorLength);
-
-            final CharSequence afterCursor = subText.subSequence(subTextSelEnd,
-                    subTextSelEnd + newAfterCursorLength);
+            final CharSequence afterCursor = subText.subSequence(selEnd,
+                    selEnd + newAfterCursorLength);
 
             newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor);
         } else {
@@ -651,15 +660,16 @@
     }
 
     /**
-     * Get <var>n</var> characters of text before the current cursor position. May be {@code null}
-     * when the protocol is not supported.
+     * Get <var>length</var> characters of text before the current cursor position. May be
+     * {@code null} when the protocol is not supported.
      *
      * @param length The expected length of the text.
      * @param flags Supplies additional options controlling how the text is returned. May be
      * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
      * @return the text before the cursor position; the length of the returned text might be less
-     * than <var>n</var>. When there is no text before the cursor, an empty string will be returned.
-     * It could also be {@code null} when the editor or system could not support this protocol.
+     * than <var>length</var>. When there is no text before the cursor, an empty string will be
+     * returned. It could also be {@code null} when the editor or system could not support this
+     * protocol.
      */
     @Nullable
     public CharSequence getInitialTextBeforeCursor(int length, int flags) {
@@ -667,8 +677,8 @@
     }
 
     /**
-     * Gets the selected text, if any. May be {@code null} when no text is selected or the selected
-     * text is way too long.
+     * Gets the selected text, if any. May be {@code null} when the protocol is not supported or the
+     * selected text is way too long.
      *
      * @param flags Supplies additional options controlling how the text is returned. May be
      * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
@@ -693,15 +703,16 @@
     }
 
     /**
-     * Get <var>n</var> characters of text after the current cursor position. May be {@code null}
-     * when the protocol is not supported.
+     * Get <var>length</var> characters of text after the current cursor position. May be
+     * {@code null} when the protocol is not supported.
      *
      * @param length The expected length of the text.
      * @param flags Supplies additional options controlling how the text is returned. May be
      * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
      * @return the text after the cursor position; the length of the returned text might be less
-     * than <var>n</var>. When there is no text after the cursor, an empty string will be returned.
-     * It could also be {@code null} when the editor or system could not support this protocol.
+     * than <var>length</var>. When there is no text after the cursor, an empty string will be
+     * returned. It could also be {@code null} when the editor or system could not support this
+     * protocol.
      */
     @Nullable
     public CharSequence getInitialTextAfterCursor(int length, int flags) {
@@ -863,7 +874,6 @@
         return 0;
     }
 
-    // TODO(b/148035211): Unit tests for this class
     static final class InitialSurroundingText implements Parcelable {
         @Nullable final CharSequence mSurroundingText;
         final int mSelectionHead;
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 703b64f..024de4d 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -80,7 +80,6 @@
             @NonNull InlinePresentationSpec presentationSpec,
             @NonNull @Source String source,
             @Nullable String[] autofillHints) {
-        // TODO(b/147394280): Add CTS test for the type field.
         return new InlineSuggestionInfo(presentationSpec, source, autofillHints, TYPE_SUGGESTION);
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f0c16aa..dbab81b1 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -664,8 +664,6 @@
          */
         @Override
         public void setCurrentRootView(ViewRootImpl rootView) {
-            // If the mCurRootView is losing window focus, release the strong reference to it
-            // so as not to prevent it from being garbage-collected.
             if (mWindowFocusGainFuture != null) {
                 mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
                 mWindowFocusGainFuture = null;
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/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index a6c83a1..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.util.SparseArray;
 import android.view.textclassifier.TextClassifier.TextClassifierType;
 
 import com.android.internal.annotations.GuardedBy;
@@ -62,8 +61,6 @@
     @Nullable
     private TextClassifier mLocalTextClassifier;
     @GuardedBy("mLock")
-    private SparseArray<TextClassifier> mSystemTextClassifiers = new SparseArray<>();
-    @GuardedBy("mLock")
     private TextClassificationSessionFactory mSessionFactory;
     @GuardedBy("mLock")
     private TextClassificationConstants mSettings;
@@ -207,23 +204,17 @@
     /** @hide */
     private TextClassifier getSystemTextClassifier(@TextClassifierType int type) {
         synchronized (mLock) {
-            if (mSystemTextClassifiers.get(type) == null
-                    && getSettings().isSystemTextClassifierEnabled()) {
+            if (getSettings().isSystemTextClassifierEnabled()) {
                 try {
-                    mSystemTextClassifiers.put(
-                            type,
-                            new SystemTextClassifier(
-                                    mContext,
-                                    getSettings(),
-                                    /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE));
-                    Log.d(LOG_TAG, "Initialized SystemTextClassifier, type = " + type);
+                    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);
                 }
             }
-            if (mSystemTextClassifiers.get(type) != null) {
-                return mSystemTextClassifiers.get(type);
-            }
             return TextClassifier.NO_OP;
         }
     }
@@ -263,7 +254,6 @@
     private void invalidateTextClassifiers() {
         synchronized (mLock) {
             mLocalTextClassifier = null;
-            mSystemTextClassifiers.clear();
         }
     }
 
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 964b6f8..0b307e6 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -32,10 +32,10 @@
     Uri getUrl();
 
     /**
-     * Gets whether the request was made for the main frame.
+     * Gets whether the request was made in order to fetch the main frame's document.
      *
-     * @return whether the request was made for the main frame. Will be {@code false} for iframes,
-     *         for example.
+     * @return whether the request was made for the main frame document. Will be
+     *         {@code false} for subresources or iframes, for example.
      */
     boolean isForMainFrame();
 
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 cbfa05c..469ab2e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -422,9 +422,6 @@
      */
     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}.
      */
@@ -11758,7 +11755,7 @@
                 }
                 AccessibilityNodeInfo.AccessibilityAction action =
                         new AccessibilityNodeInfo.AccessibilityAction(
-                                ACCESSIBILITY_ACTION_IME_ENTER, imeActionLabel);
+                                R.id.accessibilityActionImeEnter, imeActionLabel);
                 info.addAction(action);
             }
         }
@@ -12072,7 +12069,7 @@
                     }
                 }
             } return true;
-            case ACCESSIBILITY_ACTION_IME_ENTER: {
+            case R.id.accessibilityActionImeEnter: {
                 if (isFocused() && isTextEditable()) {
                     final int imeActionId = (arguments != null) ? arguments.getInt(
                             AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT,
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 78d4e61..8c52d1f 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,22 @@
      */
     @Deprecated
     public void setView(View view) {
+        mIsCustomToast = true;
         mNextView = view;
     }
 
     /**
      * Return the view.
      *
+     * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)}
+     * with a non-{@code null} view will return {@code null} here.
+     *
+     * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link
+     * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context,
+     * CharSequence, int)} or its variants will also return {@code null} here unless they had called
+     * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the
+     * toast is shown or hidden, use {@link #addCallback(Callback)}.
+     *
      * @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,7 +280,8 @@
      *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
      *      will not have custom toast views displayed.
      */
-    public View getView() {
+    @Deprecated
+    @Nullable public View getView() {
         return mNextView;
     }
 
@@ -292,6 +307,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 +319,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 +379,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 +465,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 +508,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 82eb55a..5f35622 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -471,6 +471,7 @@
             holder.mSwitchItem.setVisibility(View.GONE);
             holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
             holder.mItemView.setEnabled(enabledState);
+            holder.mItemView.setClickable(!enabledState);
         }
 
         private void updateInvisibleActionItemVisibility(@NonNull Context context,
@@ -485,6 +486,7 @@
             holder.mItemContainer.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT)
                     ? View.VISIBLE : View.GONE);
             holder.mItemView.setEnabled(true);
+            holder.mItemView.setClickable(false);
         }
 
         private void updateIntuitiveActionItemVisibility(@NonNull Context context,
@@ -504,6 +506,7 @@
             holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
             holder.mItemContainer.setVisibility(View.VISIBLE);
             holder.mItemView.setEnabled(true);
+            holder.mItemView.setClickable(false);
         }
 
         private void updateBounceActionItemVisibility(@NonNull Context context,
@@ -518,6 +521,7 @@
             holder.mSwitchItem.setVisibility(View.GONE);
             holder.mItemContainer.setVisibility(View.VISIBLE);
             holder.mItemView.setEnabled(true);
+            holder.mItemView.setClickable(false);
         }
     }
 
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/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index bbb7513..a934de3 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1487,7 +1487,6 @@
         for (int i = 0; i < tabWidget.getChildCount(); i++) {
             TextView title = tabWidget.getChildAt(i).findViewById(android.R.id.title);
             title.setTextColor(getColor(R.color.resolver_tabs_inactive_color));
-            title.setAllCaps(false);
         }
     }
 
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 22fe31e..0ccc45e 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -25,6 +25,7 @@
 import static android.system.OsConstants.S_IXGRP;
 import static android.system.OsConstants.S_IXOTH;
 
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
@@ -32,7 +33,12 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.parsing.AndroidPackage;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.incremental.IIncrementalService;
+import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Slog;
@@ -44,6 +50,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 
 /**
@@ -73,6 +80,7 @@
         private final CloseGuard mGuard = CloseGuard.get();
         private volatile boolean mClosed;
 
+        final String[] apkPaths;
         final long[] apkHandles;
         final boolean multiArch;
         final boolean extractNativeLibs;
@@ -103,9 +111,11 @@
         private static Handle create(List<String> codePaths, boolean multiArch,
                 boolean extractNativeLibs, boolean debuggable) throws IOException {
             final int size = codePaths.size();
+            final String[] apkPaths = new String[size];
             final long[] apkHandles = new long[size];
             for (int i = 0; i < size; i++) {
                 final String path = codePaths.get(i);
+                apkPaths[i] = path;
                 apkHandles[i] = nativeOpenApk(path);
                 if (apkHandles[i] == 0) {
                     // Unwind everything we've opened so far
@@ -116,7 +126,7 @@
                 }
             }
 
-            return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
+            return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable);
         }
 
         public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
@@ -127,11 +137,13 @@
                 throw new IOException("Unable to open APK " + path + " from fd " + fd);
             }
 
-            return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+            return new Handle(new String[]{path}, apkHandles, lite.multiArch,
+                    lite.extractNativeLibs, lite.debuggable);
         }
 
-        Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
-                boolean debuggable) {
+        Handle(String[] apkPaths, long[] apkHandles, boolean multiArch,
+                boolean extractNativeLibs, boolean debuggable) {
+            this.apkPaths = apkPaths;
             this.apkHandles = apkHandles;
             this.multiArch = multiArch;
             this.extractNativeLibs = extractNativeLibs;
@@ -313,40 +325,58 @@
     }
 
     public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
-            String[] abiList, boolean useIsaSubdir) throws IOException {
-        createNativeLibrarySubdir(libraryRoot);
-
+            String[] abiList, boolean useIsaSubdir, boolean isIncremental) throws IOException {
         /*
          * If this is an internal application or our nativeLibraryPath points to
          * the app-lib directory, unpack the libraries if necessary.
          */
         int abi = findSupportedAbi(handle, abiList);
-        if (abi >= 0) {
-            /*
-             * If we have a matching instruction set, construct a subdir under the native
-             * library root that corresponds to this instruction set.
-             */
-            final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);
-            final File subDir;
-            if (useIsaSubdir) {
-                final File isaSubdir = new File(libraryRoot, instructionSet);
-                createNativeLibrarySubdir(isaSubdir);
-                subDir = isaSubdir;
-            } else {
-                subDir = libraryRoot;
-            }
+        if (abi < 0) {
+            return abi;
+        }
 
-            int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]);
-            if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
-                return copyRet;
+        /*
+         * If we have a matching instruction set, construct a subdir under the native
+         * library root that corresponds to this instruction set.
+         */
+        final String supportedAbi = abiList[abi];
+        final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
+        final File subDir;
+        if (useIsaSubdir) {
+            subDir = new File(libraryRoot, instructionSet);
+        } else {
+            subDir = libraryRoot;
+        }
+
+        if (isIncremental) {
+            int res =
+                    incrementalConfigureNativeBinariesForSupportedAbi(handle, subDir, supportedAbi);
+            if (res != PackageManager.INSTALL_SUCCEEDED) {
+                // TODO(b/133435829): the caller of this function expects that we return the index
+                // to the supported ABI. However, any non-negative integer can be a valid index.
+                // We should fix this function and make sure it doesn't accidentally return an error
+                // code that can also be a valid index.
+                return res;
             }
+            return abi;
+        }
+
+        // For non-incremental, use regular extraction and copy
+        createNativeLibrarySubdir(libraryRoot);
+        if (subDir != libraryRoot) {
+            createNativeLibrarySubdir(subDir);
+        }
+
+        int copyRet = copyNativeBinaries(handle, subDir, supportedAbi);
+        if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
+            return copyRet;
         }
 
         return abi;
     }
 
     public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot,
-            String abiOverride) {
+            String abiOverride, boolean isIncremental) {
         try {
             if (handle.multiArch) {
                 // Warn if we've set an abiOverride for multi-lib packages..
@@ -359,7 +389,8 @@
                 int copyRet = PackageManager.NO_NATIVE_LIBRARIES;
                 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                     copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
-                            Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
+                            Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */,
+                            isIncremental);
                     if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
                             copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
                         Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet);
@@ -369,7 +400,8 @@
 
                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                     copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
-                            Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
+                            Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */,
+                            isIncremental);
                     if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
                             copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
                         Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet);
@@ -392,7 +424,7 @@
                 }
 
                 int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList,
-                        true /* use isa specific subdirs */);
+                        true /* use isa specific subdirs */, isIncremental);
                 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                     Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
                     return copyRet;
@@ -449,29 +481,61 @@
      * Service will create native library directories and set up native library binary files in the
      * same structure as they are in non-incremental installations.
      *
-     * @param pkg The package to be installed, including all the APK files.
-     * @param handle The pointer to an zip archive.
-     * @param libraryRoot The root directory of the native library files, e.g., lib/
-     * @param abiList The list of ABIs that are supported by the current device.
-     * @param useIsaSubdir Whether or not to set up a sub dir for the ISA.
-     * @return ABI code if installation succeeds or error code if installation fails.
+     * @param handle The Handle object that contains all apk paths.
+     * @param libSubDir The target directory to put the native library files, e.g., lib/ or lib/arm
+     * @param abi The abi that is supported by the current device.
+     * @return Integer code if installation succeeds or fails.
      */
-    public static int configureNativeBinariesForSupportedAbi(AndroidPackage pkg, Handle handle,
-            File libraryRoot, String[] abiList, boolean useIsaSubdir) {
-        int abi = findSupportedAbi(handle, abiList);
-        if (abi < 0) {
-            Slog.e(TAG, "Failed to find find matching ABI.");
-            return abi;
+    private static int incrementalConfigureNativeBinariesForSupportedAbi(Handle handle,
+            File libSubDir, String abi) {
+        final String[] apkPaths = handle.apkPaths;
+        if (apkPaths == null || apkPaths.length == 0) {
+            Slog.e(TAG, "No apks to extract native libraries from.");
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
         }
 
-        // Currently only support installations that have pre-configured native library files
-        // TODO(b/136132412): implement this after incfs supports file mapping
-        if (!libraryRoot.exists()) {
-            Slog.e(TAG, "Incremental installation currently does not configure native libs");
-            return INSTALL_FAILED_NO_MATCHING_ABIS;
+        final IBinder incrementalService = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+        if (incrementalService == null) {
+            //TODO(b/133435829): add incremental specific error codes
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        }
+        final IncrementalManager incrementalManager = new IncrementalManager(
+                IIncrementalService.Stub.asInterface(incrementalService));
+        final File apkParent = new File(apkPaths[0]).getParentFile();
+        IncrementalStorage incrementalStorage =
+                incrementalManager.openStorage(apkParent.getAbsolutePath());
+        if (incrementalStorage == null) {
+            Slog.e(TAG, "Failed to find incremental storage");
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
         }
 
-        return abi;
+        String libRelativeDir = getRelativePath(apkParent, libSubDir);
+        if (libRelativeDir == null) {
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        }
+
+        for (int i = 0; i < apkPaths.length; i++) {
+            if (!incrementalStorage.configureNativeBinaries(apkPaths[i], libRelativeDir, abi)) {
+                return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+            }
+        }
+        return PackageManager.INSTALL_SUCCEEDED;
+    }
+
+    private static String getRelativePath(File base, File target) {
+        try {
+            final Path basePath = base.toPath();
+            final Path targetPath = target.toPath();
+            final Path relativePath = basePath.relativize(targetPath);
+            if (relativePath.toString().isEmpty()) {
+                return "";
+            }
+            return relativePath.toString();
+        } catch (IllegalArgumentException ex) {
+            Slog.e(TAG, "Failed to find relative path between: " + base.getAbsolutePath()
+                    + " and: " + target.getAbsolutePath());
+            return null;
+        }
     }
 
     // We don't care about the other return values for now.
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index 48d2bc2..67ffd4d 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -56,8 +56,8 @@
      * @param event an enum implementing UiEventEnum interface.
      * @param uid the uid of the relevant app, if known (0 otherwise).
      * @param packageName the package name of the relevant app, if known (null otherwise).
-     * @param instance An identifier obtained from an InstanceIdSequence.
+     * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to log().
      */
     void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName,
-            @NonNull InstanceId instance);
+            @Nullable InstanceId instance);
 }
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index 785b2ed..4d171ec 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -41,9 +41,11 @@
     public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
             InstanceId instance) {
         final int eventID = event.getId();
-        if (eventID > 0) {
+        if ((eventID > 0)  && (instance != null)) {
             FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, eventID, uid, packageName,
                     instance.getId());
+        } else {
+            log(event, uid, packageName);
         }
     }
 }
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/java/com/android/internal/policy/TaskResizingAlgorithm.java b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java
new file mode 100644
index 0000000..1fce098
--- /dev/null
+++ b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java
@@ -0,0 +1,179 @@
+/*
+ * 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.policy;
+
+import android.annotation.IntDef;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Given a move coordinate (x, y), the original taks bounds and relevant details, calculate the new
+ * bounds.
+ *
+ * @hide
+ */
+public class TaskResizingAlgorithm {
+
+    @IntDef(flag = true,
+            value = {
+                    CTRL_NONE,
+                    CTRL_LEFT,
+                    CTRL_RIGHT,
+                    CTRL_TOP,
+                    CTRL_BOTTOM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CtrlType {}
+
+    public static final int CTRL_NONE   = 0x0;
+    public static final int CTRL_LEFT   = 0x1;
+    public static final int CTRL_RIGHT  = 0x2;
+    public static final int CTRL_TOP    = 0x4;
+    public static final int CTRL_BOTTOM = 0x8;
+
+    // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
+    // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
+    // aspect he desires.
+    @VisibleForTesting
+    public static final float MIN_ASPECT = 1.2f;
+
+    /**
+     * Given a (x, y) point and its original starting down point and its original bounds, calculate
+     * and return a new resized bound.
+     * @param x the new moved X point.
+     * @param y the new moved Y point.
+     * @param startDragX the original starting X point.
+     * @param startDragY the original starting Y point.
+     * @param originalBounds the original bound before resize.
+     * @param ctrlType The type of resize operation.
+     * @param minVisibleWidth The minimal width required for the new size.
+     * @param minVisibleHeight The minimal height required for the new size.
+     * @param maxVisibleSize The maximum size allowed.
+     * @param preserveOrientation
+     * @param startOrientationWasLandscape
+     * @return
+     */
+    public static Rect resizeDrag(float x, float y, float startDragX, float startDragY,
+            Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight,
+            Point maxVisibleSize, boolean preserveOrientation,
+            boolean startOrientationWasLandscape) {
+        // This is a resizing operation.
+        // We need to keep various constraints:
+        // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
+        // 2. The orientation is kept - if required.
+        final int deltaX = Math.round(x - startDragX);
+        final int deltaY = Math.round(y - startDragY);
+        int left = originalBounds.left;
+        int top = originalBounds.top;
+        int right = originalBounds.right;
+        int bottom = originalBounds.bottom;
+
+        // Calculate the resulting width and height of the drag operation.
+        int width = right - left;
+        int height = bottom - top;
+        if ((ctrlType & CTRL_LEFT) != 0) {
+            width = Math.max(minVisibleWidth, width - deltaX);
+        } else if ((ctrlType & CTRL_RIGHT) != 0) {
+            width = Math.max(minVisibleWidth, width + deltaX);
+        }
+        if ((ctrlType & CTRL_TOP) != 0) {
+            height = Math.max(minVisibleHeight, height - deltaY);
+        } else if ((ctrlType & CTRL_BOTTOM) != 0) {
+            height = Math.max(minVisibleHeight, height + deltaY);
+        }
+
+        // If we have to preserve the orientation - check that we are doing so.
+        final float aspect = (float) width / (float) height;
+        if (preserveOrientation && ((startOrientationWasLandscape && aspect < MIN_ASPECT)
+                || (!startOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
+            // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+            // drag axis. What ever is producing the bigger rectangle will be chosen.
+            int width1;
+            int width2;
+            int height1;
+            int height2;
+            if (startOrientationWasLandscape) {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width));
+                height1 = Math.min(height, Math.round((float) width1 / MIN_ASPECT));
+                if (height1 < minVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = minVisibleHeight;
+                    width1 = Math.max(minVisibleWidth,
+                            Math.min(maxVisibleSize.x, Math.round((float) height1 * MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height));
+                width2 = Math.max(width, Math.round((float) height2 * MIN_ASPECT));
+                if (width2 < minVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = minVisibleWidth;
+                    height2 = Math.max(minVisibleHeight,
+                            Math.min(maxVisibleSize.y, Math.round((float) width2 / MIN_ASPECT)));
+                }
+            } else {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width));
+                height1 = Math.max(height, Math.round((float) width1 * MIN_ASPECT));
+                if (height1 < minVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = minVisibleHeight;
+                    width1 = Math.max(minVisibleWidth,
+                            Math.min(maxVisibleSize.x, Math.round((float) height1 / MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height));
+                width2 = Math.min(width, Math.round((float) height2 / MIN_ASPECT));
+                if (width2 < minVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = minVisibleWidth;
+                    height2 = Math.max(minVisibleHeight,
+                            Math.min(maxVisibleSize.y, Math.round((float) width2 * MIN_ASPECT)));
+                }
+            }
+
+            // Use the bigger of the two rectangles if the major change was positive, otherwise
+            // do the opposite.
+            final boolean grows = width > (right - left) || height > (bottom - top);
+            if (grows == (width1 * height1 > width2 * height2)) {
+                width = width1;
+                height = height1;
+            } else {
+                width = width2;
+                height = height2;
+            }
+        }
+
+        // Generate the final bounds by keeping the opposite drag edge constant.
+        if ((ctrlType & CTRL_LEFT) != 0) {
+            left = right - width;
+        } else { // Note: The right might have changed - if we pulled at the right or not.
+            right = left + width;
+        }
+        if ((ctrlType & CTRL_TOP) != 0) {
+            top = bottom - height;
+        } else { // Note: The height might have changed - if we pulled at the bottom or not.
+            bottom = top + height;
+        }
+        return new Rect(left, top, right, bottom);
+    }
+}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 74b481c..2fcc1de 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -29,6 +29,7 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
 import android.text.TextUtils;
@@ -902,7 +903,6 @@
                     } break;
                     case "component-override": {
                         readComponentOverrides(parser, permFile);
-                        XmlUtils.skipCurrentTag(parser);
                     } break;
                     case "backup-transport-whitelisted-service": {
                         if (allowFeatures) {
@@ -1156,6 +1156,10 @@
             addFeature(PackageManager.FEATURE_RAM_NORMAL, 0);
         }
 
+        if (IncrementalManager.isEnabled()) {
+            addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
+        }
+
         for (String featureName : mUnavailableFeatures) {
             removeFeature(featureName);
         }
@@ -1398,8 +1402,7 @@
 
         final int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
-            String name = parser.getName();
-            if ("component".equals(name)) {
+            if ("component".equals(parser.getName())) {
                 String clsname = parser.getAttributeValue(null, "class");
                 String enabled = parser.getAttributeValue(null, "enabled");
                 if (clsname == null) {
@@ -1427,8 +1430,6 @@
                 }
 
                 componentEnabledStates.put(clsname, !"false".equals(enabled));
-            } else {
-                XmlUtils.skipCurrentTag(parser);
             }
         }
     }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index cec68df..8959d6f 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,10 +152,8 @@
                 "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_AudioDevice.cpp",
                 "android_media_AudioEffectDescriptor.cpp",
                 "android_media_AudioRecord.cpp",
                 "android_media_AudioSystem.cpp",
@@ -182,6 +182,7 @@
                 "android_hardware_UsbRequest.cpp",
                 "android_hardware_location_ActivityRecognitionHardware.cpp",
                 "android_util_FileObserver.cpp",
+                "android/graphics/GraphicsStatsService.cpp",
                 "android/graphics/SurfaceTexture.cpp",
                 "android/opengl/poly_clip.cpp", // TODO: .arm
                 "android/opengl/util.cpp",
@@ -208,6 +209,7 @@
 
             static_libs: [
                 "libasync_safe",
+                "libbinderthreadstateutils",
                 "libdmabufinfo",
                 "libgif",
                 "libseccomp_policy",
@@ -272,6 +274,7 @@
                 "libstats_jni",
                 "libstatslog",
                 "server_configurable_flags",
+                "libstatspull",
             ],
             export_shared_lib_headers: [
                 // AndroidRuntime.h depends on nativehelper/jni.h
@@ -311,11 +314,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",
             ],
         },
     },
@@ -360,7 +360,6 @@
         "android_view_RenderNode.cpp",
         "android_util_PathParser.cpp",
 
-        "android/graphics/AnimatedImageDrawable.cpp",
         "android/graphics/Bitmap.cpp",
         "android/graphics/BitmapFactory.cpp",
         "android/graphics/ByteBufferStreamAdaptor.cpp",
@@ -436,6 +435,7 @@
 
                 "android_view_TextureLayer.cpp",
                 "android_view_ThreadedRenderer.cpp",
+                "android/graphics/AnimatedImageDrawable.cpp",
                 "android/graphics/BitmapRegionDecoder.cpp",
                 "android/graphics/GIFMovie.cpp",
                 "android/graphics/Movie.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 481be24..b47b7e3 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -86,7 +86,7 @@
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
 extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
 
-extern int register_android_media_AudioDeviceAddress(JNIEnv *env);
+extern int register_android_media_AudioDevice(JNIEnv* env);
 extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
 extern int register_android_media_AudioRecord(JNIEnv *env);
 extern int register_android_media_AudioSystem(JNIEnv *env);
@@ -209,9 +209,11 @@
 static const char* PROFILE_BOOT_CLASS_PATH = "profilebootclasspath";
 
 // Feature flag name for running the JIT in Zygote experiment, b/119800099.
-static const char* ENABLE_APEX_IMAGE = "enable_apex_image";
-// Flag to pass to the runtime when using the apex image.
-static const char* kApexImageOption = "-Ximage:/system/framework/apex.art";
+// TODO: Rename the server-level flag or remove.
+static const char* ENABLE_JITZYGOTE_IMAGE = "enable_apex_image";
+// Flag to pass to the runtime when using the JIT Zygote image.
+static const char* kJitZygoteImageOption =
+        "-Ximage:boot.art:/nonx/boot-framework.art!/system/etc/boot-image.prof";
 
 // Feature flag name for disabling lock profiling.
 static const char* DISABLE_LOCK_PROFILING = "disable_lock_profiling";
@@ -631,11 +633,11 @@
     char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX];
     char foregroundHeapGrowthMultiplierOptsBuf[
             sizeof("-XX:ForegroundHeapGrowthMultiplier=")-1 + PROPERTY_VALUE_MAX];
+    char finalizerTimeoutMsOptsBuf[sizeof("-XX:FinalizerTimeoutMs=")-1 + PROPERTY_VALUE_MAX];
+    char threadSuspendTimeoutOptsBuf[sizeof("-XX:ThreadSuspendTimeout=")-1 + PROPERTY_VALUE_MAX];
     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];
@@ -689,16 +691,16 @@
         addOption("-Xjitsaveprofilinginfo");
     }
 
-    std::string use_apex_image_flag =
-        server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
-                                                             ENABLE_APEX_IMAGE,
-                                                             /*default_value=*/ "");
+    std::string use_jitzygote_image_flag =
+            server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
+                                                                 ENABLE_JITZYGOTE_IMAGE,
+                                                                 /*default_value=*/"");
     // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods.
     // Also use the APEX boot image if it's explicitly enabled via configuration flag.
-    const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true");
+    const bool use_apex_image = profile_boot_class_path || (use_jitzygote_image_flag == "true");
     if (use_apex_image) {
-        addOption(kApexImageOption);
-        ALOGI("Using Apex boot image: '%s'\n", kApexImageOption);
+        ALOGI("Using JIT Zygote image: '%s'\n", kJitZygoteImageOption);
+        addOption(kJitZygoteImageOption);
     } else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) {
         ALOGI("Using dalvik.vm.boot-image: '%s'\n", bootImageBuf);
     } else {
@@ -786,7 +788,15 @@
     parseRuntimeOption("dalvik.vm.foreground-heap-growth-multiplier",
                        foregroundHeapGrowthMultiplierOptsBuf,
                        "-XX:ForegroundHeapGrowthMultiplier=");
-
+    /*
+     * Finalizer and thread suspend timeouts.
+     */
+    parseRuntimeOption("dalvik.vm.finalizer-timeout-ms",
+                       finalizerTimeoutMsOptsBuf,
+                       "-XX:FinalizerTimeoutMs=");
+    parseRuntimeOption("dalvik.vm.thread-suspend-timeout-ms",
+                       threadSuspendTimeoutOptsBuf,
+                       "-XX:ThreadSuspendTimeout=");
     /*
      * JIT related options.
      */
@@ -885,88 +895,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 +942,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 +1046,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') {
@@ -1441,140 +1436,140 @@
 }
 
 static const RegJNIRec gRegJNI[] = {
-    REG_JNI(register_com_android_internal_os_RuntimeInit),
-    REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
-    REG_JNI(register_android_os_SystemClock),
-    REG_JNI(register_android_util_EventLog),
-    REG_JNI(register_android_util_Log),
-    REG_JNI(register_android_util_MemoryIntArray),
-    REG_JNI(register_android_util_StatsLog),
-    REG_JNI(register_android_util_StatsLogInternal),
-    REG_JNI(register_android_app_admin_SecurityLog),
-    REG_JNI(register_android_content_AssetManager),
-    REG_JNI(register_android_content_StringBlock),
-    REG_JNI(register_android_content_XmlBlock),
-    REG_JNI(register_android_content_res_ApkAssets),
-    REG_JNI(register_android_text_AndroidCharacter),
-    REG_JNI(register_android_text_Hyphenator),
-    REG_JNI(register_android_view_InputDevice),
-    REG_JNI(register_android_view_KeyCharacterMap),
-    REG_JNI(register_android_os_Process),
-    REG_JNI(register_android_os_SystemProperties),
-    REG_JNI(register_android_os_Binder),
-    REG_JNI(register_android_os_Parcel),
-    REG_JNI(register_android_os_HidlMemory),
-    REG_JNI(register_android_os_HidlSupport),
-    REG_JNI(register_android_os_HwBinder),
-    REG_JNI(register_android_os_HwBlob),
-    REG_JNI(register_android_os_HwParcel),
-    REG_JNI(register_android_os_HwRemoteBinder),
-    REG_JNI(register_android_os_NativeHandle),
-    REG_JNI(register_android_os_storage_StorageManager),
-    REG_JNI(register_android_os_VintfObject),
-    REG_JNI(register_android_os_VintfRuntimeInfo),
-    REG_JNI(register_android_service_DataLoaderService),
-    REG_JNI(register_android_view_DisplayEventReceiver),
-    REG_JNI(register_android_view_RenderNodeAnimator),
-    REG_JNI(register_android_view_InputApplicationHandle),
-    REG_JNI(register_android_view_InputWindowHandle),
-    REG_JNI(register_android_view_Surface),
-    REG_JNI(register_android_view_SurfaceControl),
-    REG_JNI(register_android_view_SurfaceSession),
-    REG_JNI(register_android_view_CompositionSamplingListener),
-    REG_JNI(register_android_view_TextureView),
-    REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper),
-    REG_JNI(register_com_google_android_gles_jni_EGLImpl),
-    REG_JNI(register_com_google_android_gles_jni_GLImpl),
-    REG_JNI(register_android_opengl_jni_EGL14),
-    REG_JNI(register_android_opengl_jni_EGL15),
-    REG_JNI(register_android_opengl_jni_EGLExt),
-    REG_JNI(register_android_opengl_jni_GLES10),
-    REG_JNI(register_android_opengl_jni_GLES10Ext),
-    REG_JNI(register_android_opengl_jni_GLES11),
-    REG_JNI(register_android_opengl_jni_GLES11Ext),
-    REG_JNI(register_android_opengl_jni_GLES20),
-    REG_JNI(register_android_opengl_jni_GLES30),
-    REG_JNI(register_android_opengl_jni_GLES31),
-    REG_JNI(register_android_opengl_jni_GLES31Ext),
-    REG_JNI(register_android_opengl_jni_GLES32),
-    REG_JNI(register_android_graphics_classes),
-    REG_JNI(register_android_graphics_BLASTBufferQueue),
-    REG_JNI(register_android_graphics_GraphicBuffer),
-    REG_JNI(register_android_database_CursorWindow),
-    REG_JNI(register_android_database_SQLiteConnection),
-    REG_JNI(register_android_database_SQLiteGlobal),
-    REG_JNI(register_android_database_SQLiteDebug),
-    REG_JNI(register_android_os_Debug),
-    REG_JNI(register_android_os_FileObserver),
-    REG_JNI(register_android_os_GraphicsEnvironment),
-    REG_JNI(register_android_os_MessageQueue),
-    REG_JNI(register_android_os_SELinux),
-    REG_JNI(register_android_os_Trace),
-    REG_JNI(register_android_os_UEventObserver),
-    REG_JNI(register_android_net_LocalSocketImpl),
-    REG_JNI(register_android_net_NetworkUtils),
-    REG_JNI(register_android_os_MemoryFile),
-    REG_JNI(register_android_os_SharedMemory),
-    REG_JNI(register_android_os_incremental_IncrementalManager),
-    REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
-    REG_JNI(register_com_android_internal_os_Zygote),
-    REG_JNI(register_com_android_internal_os_ZygoteInit),
-    REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
-    REG_JNI(register_android_hardware_Camera),
-    REG_JNI(register_android_hardware_camera2_CameraMetadata),
-    REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
-    REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
-    REG_JNI(register_android_hardware_camera2_DngCreator),
-    REG_JNI(register_android_hardware_HardwareBuffer),
-    REG_JNI(register_android_hardware_SensorManager),
-    REG_JNI(register_android_hardware_SerialPort),
-    REG_JNI(register_android_hardware_UsbDevice),
-    REG_JNI(register_android_hardware_UsbDeviceConnection),
-    REG_JNI(register_android_hardware_UsbRequest),
-    REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
-    REG_JNI(register_android_media_AudioDeviceAddress),
-    REG_JNI(register_android_media_AudioEffectDescriptor),
-    REG_JNI(register_android_media_AudioSystem),
-    REG_JNI(register_android_media_AudioRecord),
-    REG_JNI(register_android_media_AudioTrack),
-    REG_JNI(register_android_media_AudioAttributes),
-    REG_JNI(register_android_media_AudioProductStrategies),
-    REG_JNI(register_android_media_AudioVolumeGroups),
-    REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
-    REG_JNI(register_android_media_MediaMetrics),
-    REG_JNI(register_android_media_MicrophoneInfo),
-    REG_JNI(register_android_media_RemoteDisplay),
-    REG_JNI(register_android_media_ToneGenerator),
-    REG_JNI(register_android_media_midi),
+        REG_JNI(register_com_android_internal_os_RuntimeInit),
+        REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
+        REG_JNI(register_android_os_SystemClock),
+        REG_JNI(register_android_util_EventLog),
+        REG_JNI(register_android_util_Log),
+        REG_JNI(register_android_util_MemoryIntArray),
+        REG_JNI(register_android_util_StatsLog),
+        REG_JNI(register_android_util_StatsLogInternal),
+        REG_JNI(register_android_app_admin_SecurityLog),
+        REG_JNI(register_android_content_AssetManager),
+        REG_JNI(register_android_content_StringBlock),
+        REG_JNI(register_android_content_XmlBlock),
+        REG_JNI(register_android_content_res_ApkAssets),
+        REG_JNI(register_android_text_AndroidCharacter),
+        REG_JNI(register_android_text_Hyphenator),
+        REG_JNI(register_android_view_InputDevice),
+        REG_JNI(register_android_view_KeyCharacterMap),
+        REG_JNI(register_android_os_Process),
+        REG_JNI(register_android_os_SystemProperties),
+        REG_JNI(register_android_os_Binder),
+        REG_JNI(register_android_os_Parcel),
+        REG_JNI(register_android_os_HidlMemory),
+        REG_JNI(register_android_os_HidlSupport),
+        REG_JNI(register_android_os_HwBinder),
+        REG_JNI(register_android_os_HwBlob),
+        REG_JNI(register_android_os_HwParcel),
+        REG_JNI(register_android_os_HwRemoteBinder),
+        REG_JNI(register_android_os_NativeHandle),
+        REG_JNI(register_android_os_storage_StorageManager),
+        REG_JNI(register_android_os_VintfObject),
+        REG_JNI(register_android_os_VintfRuntimeInfo),
+        REG_JNI(register_android_service_DataLoaderService),
+        REG_JNI(register_android_view_DisplayEventReceiver),
+        REG_JNI(register_android_view_RenderNodeAnimator),
+        REG_JNI(register_android_view_InputApplicationHandle),
+        REG_JNI(register_android_view_InputWindowHandle),
+        REG_JNI(register_android_view_Surface),
+        REG_JNI(register_android_view_SurfaceControl),
+        REG_JNI(register_android_view_SurfaceSession),
+        REG_JNI(register_android_view_CompositionSamplingListener),
+        REG_JNI(register_android_view_TextureView),
+        REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper),
+        REG_JNI(register_com_google_android_gles_jni_EGLImpl),
+        REG_JNI(register_com_google_android_gles_jni_GLImpl),
+        REG_JNI(register_android_opengl_jni_EGL14),
+        REG_JNI(register_android_opengl_jni_EGL15),
+        REG_JNI(register_android_opengl_jni_EGLExt),
+        REG_JNI(register_android_opengl_jni_GLES10),
+        REG_JNI(register_android_opengl_jni_GLES10Ext),
+        REG_JNI(register_android_opengl_jni_GLES11),
+        REG_JNI(register_android_opengl_jni_GLES11Ext),
+        REG_JNI(register_android_opengl_jni_GLES20),
+        REG_JNI(register_android_opengl_jni_GLES30),
+        REG_JNI(register_android_opengl_jni_GLES31),
+        REG_JNI(register_android_opengl_jni_GLES31Ext),
+        REG_JNI(register_android_opengl_jni_GLES32),
+        REG_JNI(register_android_graphics_classes),
+        REG_JNI(register_android_graphics_BLASTBufferQueue),
+        REG_JNI(register_android_graphics_GraphicBuffer),
+        REG_JNI(register_android_database_CursorWindow),
+        REG_JNI(register_android_database_SQLiteConnection),
+        REG_JNI(register_android_database_SQLiteGlobal),
+        REG_JNI(register_android_database_SQLiteDebug),
+        REG_JNI(register_android_os_Debug),
+        REG_JNI(register_android_os_FileObserver),
+        REG_JNI(register_android_os_GraphicsEnvironment),
+        REG_JNI(register_android_os_MessageQueue),
+        REG_JNI(register_android_os_SELinux),
+        REG_JNI(register_android_os_Trace),
+        REG_JNI(register_android_os_UEventObserver),
+        REG_JNI(register_android_net_LocalSocketImpl),
+        REG_JNI(register_android_net_NetworkUtils),
+        REG_JNI(register_android_os_MemoryFile),
+        REG_JNI(register_android_os_SharedMemory),
+        REG_JNI(register_android_os_incremental_IncrementalManager),
+        REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
+        REG_JNI(register_com_android_internal_os_Zygote),
+        REG_JNI(register_com_android_internal_os_ZygoteInit),
+        REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
+        REG_JNI(register_android_hardware_Camera),
+        REG_JNI(register_android_hardware_camera2_CameraMetadata),
+        REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
+        REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
+        REG_JNI(register_android_hardware_camera2_DngCreator),
+        REG_JNI(register_android_hardware_HardwareBuffer),
+        REG_JNI(register_android_hardware_SensorManager),
+        REG_JNI(register_android_hardware_SerialPort),
+        REG_JNI(register_android_hardware_UsbDevice),
+        REG_JNI(register_android_hardware_UsbDeviceConnection),
+        REG_JNI(register_android_hardware_UsbRequest),
+        REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
+        REG_JNI(register_android_media_AudioDevice),
+        REG_JNI(register_android_media_AudioEffectDescriptor),
+        REG_JNI(register_android_media_AudioSystem),
+        REG_JNI(register_android_media_AudioRecord),
+        REG_JNI(register_android_media_AudioTrack),
+        REG_JNI(register_android_media_AudioAttributes),
+        REG_JNI(register_android_media_AudioProductStrategies),
+        REG_JNI(register_android_media_AudioVolumeGroups),
+        REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+        REG_JNI(register_android_media_MediaMetrics),
+        REG_JNI(register_android_media_MicrophoneInfo),
+        REG_JNI(register_android_media_RemoteDisplay),
+        REG_JNI(register_android_media_ToneGenerator),
+        REG_JNI(register_android_media_midi),
 
-    REG_JNI(register_android_opengl_classes),
-    REG_JNI(register_android_server_NetworkManagementSocketTagger),
-    REG_JNI(register_android_ddm_DdmHandleNativeHeap),
-    REG_JNI(register_android_backup_BackupDataInput),
-    REG_JNI(register_android_backup_BackupDataOutput),
-    REG_JNI(register_android_backup_FileBackupHelperBase),
-    REG_JNI(register_android_backup_BackupHelperDispatcher),
-    REG_JNI(register_android_app_backup_FullBackup),
-    REG_JNI(register_android_app_Activity),
-    REG_JNI(register_android_app_ActivityThread),
-    REG_JNI(register_android_app_NativeActivity),
-    REG_JNI(register_android_util_jar_StrictJarFile),
-    REG_JNI(register_android_view_InputChannel),
-    REG_JNI(register_android_view_InputEventReceiver),
-    REG_JNI(register_android_view_InputEventSender),
-    REG_JNI(register_android_view_InputQueue),
-    REG_JNI(register_android_view_KeyEvent),
-    REG_JNI(register_android_view_MotionEvent),
-    REG_JNI(register_android_view_PointerIcon),
-    REG_JNI(register_android_view_VelocityTracker),
+        REG_JNI(register_android_opengl_classes),
+        REG_JNI(register_android_server_NetworkManagementSocketTagger),
+        REG_JNI(register_android_ddm_DdmHandleNativeHeap),
+        REG_JNI(register_android_backup_BackupDataInput),
+        REG_JNI(register_android_backup_BackupDataOutput),
+        REG_JNI(register_android_backup_FileBackupHelperBase),
+        REG_JNI(register_android_backup_BackupHelperDispatcher),
+        REG_JNI(register_android_app_backup_FullBackup),
+        REG_JNI(register_android_app_Activity),
+        REG_JNI(register_android_app_ActivityThread),
+        REG_JNI(register_android_app_NativeActivity),
+        REG_JNI(register_android_util_jar_StrictJarFile),
+        REG_JNI(register_android_view_InputChannel),
+        REG_JNI(register_android_view_InputEventReceiver),
+        REG_JNI(register_android_view_InputEventSender),
+        REG_JNI(register_android_view_InputQueue),
+        REG_JNI(register_android_view_KeyEvent),
+        REG_JNI(register_android_view_MotionEvent),
+        REG_JNI(register_android_view_PointerIcon),
+        REG_JNI(register_android_view_VelocityTracker),
 
-    REG_JNI(register_android_content_res_ObbScanner),
-    REG_JNI(register_android_content_res_Configuration),
+        REG_JNI(register_android_content_res_ObbScanner),
+        REG_JNI(register_android_content_res_Configuration),
 
-    REG_JNI(register_android_animation_PropertyValuesHolder),
-    REG_JNI(register_android_security_Scrypt),
-    REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
-    REG_JNI(register_com_android_internal_os_FuseAppLoop),
+        REG_JNI(register_android_animation_PropertyValuesHolder),
+        REG_JNI(register_android_security_Scrypt),
+        REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
+        REG_JNI(register_com_android_internal_os_FuseAppLoop),
 };
 
 /*
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/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 8fc6afa..2a56fd6d 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -34,9 +34,9 @@
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
-#include <utils/FatVector.h>
 #include <minikin/FontFamily.h>
 #include <minikin/LocaleList.h>
+#include <ui/FatVector.h>
 
 #include <memory>
 
@@ -109,7 +109,7 @@
 
 static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
         jint weight, jint italic) {
-    uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
+    FatVector<SkFontArguments::Axis, 2> skiaAxes;
     for (const auto& axis : builder->axes) {
         skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
     }
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/graphics/GraphicsStatsService.cpp b/core/jni/android/graphics/GraphicsStatsService.cpp
new file mode 100644
index 0000000..ef0aacc
--- /dev/null
+++ b/core/jni/android/graphics/GraphicsStatsService.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 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 "GraphicsStatsService"
+
+#include <JankTracker.h>
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <service/GraphicsStatsService.h>
+#include <stats_event.h>
+#include <stats_pull_atom_callback.h>
+#include <statslog.h>
+#include "core_jni_helpers.h"
+
+namespace android {
+
+using namespace android::uirenderer;
+
+static jint getAshmemSize(JNIEnv*, jobject) {
+    return sizeof(ProfileData);
+}
+
+static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
+    GraphicsStatsService::Dump* dump =
+            GraphicsStatsService::createDump(fd,
+                                             isProto ? GraphicsStatsService::DumpType::Protobuf
+                                                     : GraphicsStatsService::DumpType::Text);
+    return reinterpret_cast<jlong>(dump);
+}
+
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
+                      jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+    std::string path;
+    const ProfileData* data = nullptr;
+    LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
+    ScopedByteArrayRO buffer{env};
+    if (jdata != nullptr) {
+        buffer.reset(jdata);
+        LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+                            "Buffer size %zu doesn't match expected %zu!", buffer.size(),
+                            sizeof(ProfileData));
+        data = reinterpret_cast<const ProfileData*>(buffer.get());
+    }
+    if (jpath != nullptr) {
+        ScopedUtfChars pathChars(env, jpath);
+        LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(),
+                            "Failed to get path chars");
+        path.assign(pathChars.c_str(), pathChars.size());
+    }
+    ScopedUtfChars packageChars(env, jpackage);
+    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(),
+                        "Failed to get path chars");
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
+
+    const std::string package(packageChars.c_str(), packageChars.size());
+    GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+}
+
+static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
+    ScopedUtfChars pathChars(env, jpath);
+    LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+    const std::string path(pathChars.c_str(), pathChars.size());
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    GraphicsStatsService::addToDump(dump, path);
+}
+
+static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    GraphicsStatsService::finishDump(dump);
+}
+
+static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulledData,
+                               jboolean lastFullDay) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    AStatsEventList* data = reinterpret_cast<AStatsEventList*>(pulledData);
+    GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE);
+}
+
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+                       jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+    ScopedByteArrayRO buffer(env, jdata);
+    LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+                        "Buffer size %zu doesn't match expected %zu!", buffer.size(),
+                        sizeof(ProfileData));
+    ScopedUtfChars pathChars(env, jpath);
+    LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+    ScopedUtfChars packageChars(env, jpackage);
+    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(),
+                        "Failed to get path chars");
+
+    const std::string path(pathChars.c_str(), pathChars.size());
+    const std::string package(packageChars.c_str(), packageChars.size());
+    const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
+    GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+}
+
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+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 AStatsManager_PULL_SKIP;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        env->CallVoidMethod(gGraphicsStatsServiceObject,
+                            gGraphicsStatsService_pullGraphicsStatsMethodID,
+                            (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE),
+                            reinterpret_cast<jlong>(data));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return AStatsManager_PULL_SKIP;
+        }
+    }
+    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);
+    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) {
+    AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS);
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
+static const JNINativeMethod sMethods[] =
+        {{"nGetAshmemSize", "()I", (void*)getAshmemSize},
+         {"nCreateDump", "(IZ)J", (void*)createDump},
+         {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump},
+         {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
+         {"nFinishDump", "(J)V", (void*)finishDump},
+         {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
+         {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer},
+         {"nativeInit", "()V", (void*)nativeInit},
+         {"nativeDestructor", "()V", (void*)nativeDestructor}};
+
+int register_android_graphics_GraphicsStatsService(JNIEnv* env) {
+    jclass graphicsStatsService_class =
+            FindClassOrDie(env, "android/graphics/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID =
+            GetMethodIDOrDie(env, graphicsStatsService_class, "pullGraphicsStats", "(ZJ)V");
+    return jniRegisterNativeMethods(env, "android/graphics/GraphicsStatsService", sMethods,
+                                    NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android/graphics/fonts/Font.cpp b/core/jni/android/graphics/fonts/Font.cpp
index bb0654d..8d84e87 100644
--- a/core/jni/android/graphics/fonts/Font.cpp
+++ b/core/jni/android/graphics/fonts/Font.cpp
@@ -33,8 +33,8 @@
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
-#include <utils/FatVector.h>
 #include <minikin/FontFamily.h>
+#include <ui/FatVector.h>
 
 #include <memory>
 
@@ -157,7 +157,7 @@
     sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
 
-    uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
+    FatVector<SkFontArguments::Axis, 2> skiaAxes;
     for (const auto& axis : builder->axes) {
         skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
     }
diff --git a/core/jni/android_media_AudioDevice.cpp b/core/jni/android_media_AudioDevice.cpp
new file mode 100644
index 0000000..f6a0e4b
--- /dev/null
+++ b/core/jni/android_media_AudioDevice.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "android_media_AudioDevice.h"
+#include "android_media_AudioErrors.h"
+#include "core_jni_helpers.h"
+
+#include <media/AudioDeviceTypeAddr.h>
+
+using namespace android;
+
+static jclass gAudioDeviceClass;
+static jmethodID gAudioDeviceCstor;
+
+namespace android {
+
+jint createAudioDeviceFromNative(JNIEnv *env, jobject *jAudioDevice,
+                                 const AudioDeviceTypeAddr *devTypeAddr) {
+    jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+    jint jNativeType = (jint)devTypeAddr->mType;
+    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
+
+    *jAudioDevice =
+            env->NewObject(gAudioDeviceClass, gAudioDeviceCstor, jNativeType, jAddress.get());
+
+    return jStatus;
+}
+
+} // namespace android
+
+int register_android_media_AudioDevice(JNIEnv *env) {
+    jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDevice");
+    gAudioDeviceClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass);
+    gAudioDeviceCstor =
+            GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>", "(ILjava/lang/String;)V");
+
+    return 0;
+}
diff --git a/core/jni/android_media_AudioDeviceAddress.h b/core/jni/android_media_AudioDevice.h
similarity index 71%
rename from core/jni/android_media_AudioDeviceAddress.h
rename to core/jni/android_media_AudioDevice.h
index c66b179..fc92334 100644
--- a/core/jni/android_media_AudioDeviceAddress.h
+++ b/core/jni/android_media_AudioDevice.h
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_MEDIA_AUDIODEVICEADDRESS_H
-#define ANDROID_MEDIA_AUDIODEVICEADDRESS_H
+#ifndef ANDROID_MEDIA_AUDIODEVICE_H
+#define ANDROID_MEDIA_AUDIODEVICE_H
 
-#include <system/audio.h>
 #include <media/AudioDeviceTypeAddr.h>
+#include <system/audio.h>
 
 #include "jni.h"
 
 namespace android {
 
-// Create a Java AudioDeviceAddress instance from a C++ AudioDeviceTypeAddress
+// Create a Java AudioDevice instance from a C++ AudioDeviceTypeAddress
 
-extern jint createAudioDeviceAddressFromNative(JNIEnv *env, jobject *jAudioDeviceAddress,
-        const AudioDeviceTypeAddr *devTypeAddr);
+extern jint createAudioDeviceFromNative(JNIEnv *env, jobject *jAudioDevice,
+                                        const AudioDeviceTypeAddr *devTypeAddr);
 } // namespace android
 
 #endif
\ No newline at end of file
diff --git a/core/jni/android_media_AudioDeviceAddress.cpp b/core/jni/android_media_AudioDeviceAddress.cpp
deleted file mode 100644
index 5f39f7e..0000000
--- a/core/jni/android_media_AudioDeviceAddress.cpp
+++ /dev/null
@@ -1,53 +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.
- */
-
-#include "core_jni_helpers.h"
-#include "android_media_AudioDeviceAddress.h"
-#include "android_media_AudioErrors.h"
-
-#include <media/AudioDeviceTypeAddr.h>
-
-using namespace android;
-
-static jclass gAudioDeviceAddressClass;
-static jmethodID gAudioDeviceAddressCstor;
-
-namespace android {
-
-jint createAudioDeviceAddressFromNative(
-        JNIEnv *env, jobject *jAudioDeviceAddress,
-        const AudioDeviceTypeAddr *devTypeAddr) {
-    jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
-    jint jNativeType = (jint)devTypeAddr->mType;
-    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
-
-    *jAudioDeviceAddress = env->NewObject(gAudioDeviceAddressClass, gAudioDeviceAddressCstor,
-            jNativeType, jAddress.get());
-
-    return jStatus;
-}
-
-}
-
-int register_android_media_AudioDeviceAddress(JNIEnv *env)
-{
-    jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDeviceAddress");
-    gAudioDeviceAddressClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass);
-    gAudioDeviceAddressCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>",
-                                                "(ILjava/lang/String;)V");
-
-    return 0;
-}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 0156e23..b4590f4 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -26,12 +26,6 @@
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
-#include "android_media_AudioAttributes.h"
-#include "android_media_AudioDeviceAddress.h"
-#include "android_media_AudioEffectDescriptor.h"
-#include "android_media_AudioErrors.h"
-#include "android_media_AudioFormat.h"
-#include "android_media_MicrophoneInfo.h"
 #include <audiomanager/AudioManager.h>
 #include <media/AudioPolicy.h>
 #include <media/AudioSystem.h>
@@ -39,6 +33,12 @@
 #include <nativehelper/ScopedLocalRef.h>
 #include <system/audio.h>
 #include <system/audio_policy.h>
+#include "android_media_AudioAttributes.h"
+#include "android_media_AudioDevice.h"
+#include "android_media_AudioEffectDescriptor.h"
+#include "android_media_AudioErrors.h"
+#include "android_media_AudioFormat.h"
+#include "android_media_MicrophoneInfo.h"
 
 // ----------------------------------------------------------------------------
 
@@ -2349,7 +2349,7 @@
         jint strategy, jobjectArray jDeviceArray)
 {
     if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) {
-        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        ALOGE("%s invalid array to store AudioDevice", __FUNCTION__);
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
 
@@ -2359,10 +2359,10 @@
     if (status != NO_ERROR) {
         return (jint) status;
     }
-    jobject jAudioDeviceAddress = NULL;
-    jint jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &elDevice);
+    jobject jAudioDevice = NULL;
+    jint jStatus = createAudioDeviceFromNative(env, &jAudioDevice, &elDevice);
     if (jStatus == AUDIO_JAVA_SUCCESS) {
-        env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAddress);
+        env->SetObjectArrayElement(jDeviceArray, 0, jAudioDevice);
     }
     return jStatus;
 }
@@ -2377,7 +2377,7 @@
     // with reverse JNI to make the array grow as need as this would be less efficient, and some
     // components call this method often
     if (jDeviceArray == nullptr || maxResultSize == 0) {
-        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        ALOGE("%s invalid array to store AudioDevice", __FUNCTION__);
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
 
@@ -2398,105 +2398,133 @@
         return AUDIO_JAVA_INVALID_OPERATION;
     }
     size_t index = 0;
-    jobject jAudioDeviceAddress = NULL;
+    jobject jAudioDevice = NULL;
     for (const auto& device : devices) {
-        jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+        jStatus = createAudioDeviceFromNative(env, &jAudioDevice, &device);
         if (jStatus != AUDIO_JAVA_SUCCESS) {
             return jStatus;
         }
-        env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+        env->SetObjectArrayElement(jDeviceArray, index++, jAudioDevice);
     }
     return jStatus;
 }
 
 // ----------------------------------------------------------------------------
 
-static const JNINativeMethod gMethods[] = {
-    {"setParameters",        "(Ljava/lang/String;)I", (void *)android_media_AudioSystem_setParameters},
-    {"getParameters",        "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_AudioSystem_getParameters},
-    {"muteMicrophone",      "(Z)I",     (void *)android_media_AudioSystem_muteMicrophone},
-    {"isMicrophoneMuted",   "()Z",      (void *)android_media_AudioSystem_isMicrophoneMuted},
-    {"isStreamActive",      "(II)Z",    (void *)android_media_AudioSystem_isStreamActive},
-    {"isStreamActiveRemotely","(II)Z",  (void *)android_media_AudioSystem_isStreamActiveRemotely},
-    {"isSourceActive",      "(I)Z",     (void *)android_media_AudioSystem_isSourceActive},
-    {"newAudioSessionId",   "()I",      (void *)android_media_AudioSystem_newAudioSessionId},
-    {"newAudioPlayerId",    "()I",      (void *)android_media_AudioSystem_newAudioPlayerId},
-    {"newAudioRecorderId",  "()I",      (void *)android_media_AudioSystem_newAudioRecorderId},
-    {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
-    {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
-    {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_handleDeviceConfigChange},
-    {"setPhoneState",       "(I)I",     (void *)android_media_AudioSystem_setPhoneState},
-    {"setForceUse",         "(II)I",    (void *)android_media_AudioSystem_setForceUse},
-    {"getForceUse",         "(I)I",     (void *)android_media_AudioSystem_getForceUse},
-    {"initStreamVolume",    "(III)I",   (void *)android_media_AudioSystem_initStreamVolume},
-    {"setStreamVolumeIndex","(III)I",   (void *)android_media_AudioSystem_setStreamVolumeIndex},
-    {"getStreamVolumeIndex","(II)I",    (void *)android_media_AudioSystem_getStreamVolumeIndex},
-    {"setVolumeIndexForAttributes","(Landroid/media/AudioAttributes;II)I",   (void *)android_media_AudioSystem_setVolumeIndexForAttributes},
-    {"getVolumeIndexForAttributes","(Landroid/media/AudioAttributes;I)I",    (void *)android_media_AudioSystem_getVolumeIndexForAttributes},
-    {"getMinVolumeIndexForAttributes","(Landroid/media/AudioAttributes;)I",    (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes},
-    {"getMaxVolumeIndexForAttributes","(Landroid/media/AudioAttributes;)I",    (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes},
-    {"setMasterVolume",     "(F)I",     (void *)android_media_AudioSystem_setMasterVolume},
-    {"getMasterVolume",     "()F",      (void *)android_media_AudioSystem_getMasterVolume},
-    {"setMasterMute",       "(Z)I",     (void *)android_media_AudioSystem_setMasterMute},
-    {"getMasterMute",       "()Z",      (void *)android_media_AudioSystem_getMasterMute},
-    {"setMasterMono",       "(Z)I",     (void *)android_media_AudioSystem_setMasterMono},
-    {"getMasterMono",       "()Z",      (void *)android_media_AudioSystem_getMasterMono},
-    {"setMasterBalance",    "(F)I",     (void *)android_media_AudioSystem_setMasterBalance},
-    {"getMasterBalance",    "()F",      (void *)android_media_AudioSystem_getMasterBalance},
-    {"getDevicesForStream", "(I)I",     (void *)android_media_AudioSystem_getDevicesForStream},
-    {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
-    {"getPrimaryOutputFrameCount",   "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
-    {"getOutputLatency",    "(I)I",     (void *)android_media_AudioSystem_getOutputLatency},
-    {"setLowRamDevice",     "(ZJ)I",    (void *)android_media_AudioSystem_setLowRamDevice},
-    {"checkAudioFlinger",    "()I",     (void *)android_media_AudioSystem_checkAudioFlinger},
-    {"listAudioPorts",      "(Ljava/util/ArrayList;[I)I",
-                                                (void *)android_media_AudioSystem_listAudioPorts},
-    {"createAudioPatch",    "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
-                                            (void *)android_media_AudioSystem_createAudioPatch},
-    {"releaseAudioPatch",   "(Landroid/media/AudioPatch;)I",
-                                            (void *)android_media_AudioSystem_releaseAudioPatch},
-    {"listAudioPatches",    "(Ljava/util/ArrayList;[I)I",
-                                                (void *)android_media_AudioSystem_listAudioPatches},
-    {"setAudioPortConfig",   "(Landroid/media/AudioPortConfig;)I",
-                                            (void *)android_media_AudioSystem_setAudioPortConfig},
-    {"startAudioSource",    "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
-                                            (void *)android_media_AudioSystem_startAudioSource},
-    {"stopAudioSource",     "(I)I", (void *)android_media_AudioSystem_stopAudioSource},
-    {"getAudioHwSyncForSession", "(I)I",
-                                    (void *)android_media_AudioSystem_getAudioHwSyncForSession},
-    {"registerPolicyMixes",    "(Ljava/util/ArrayList;Z)I",
-                                            (void *)android_media_AudioSystem_registerPolicyMixes},
-    {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
-                                        (void *)android_media_AudioSystem_setUidDeviceAffinities},
-    {"removeUidDeviceAffinities", "(I)I",
-                                        (void *)android_media_AudioSystem_removeUidDeviceAffinities},
-    {"native_register_dynamic_policy_callback", "()V",
-                                    (void *)android_media_AudioSystem_registerDynPolicyCallback},
-    {"native_register_recording_callback", "()V",
-                                    (void *)android_media_AudioSystem_registerRecordingCallback},
-    {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
-    {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
-    {"native_is_offload_supported", "(IIIII)Z", (void *)android_media_AudioSystem_isOffloadSupported},
-    {"getMicrophones", "(Ljava/util/ArrayList;)I", (void *)android_media_AudioSystem_getMicrophones},
-    {"getSurroundFormats", "(Ljava/util/Map;Z)I", (void *)android_media_AudioSystem_getSurroundFormats},
-    {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled},
-    {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid},
-    {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
-    {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported},
-    {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
-                    (void*)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
-    {"setSupportedSystemUsages", "([I)I", (void *)android_media_AudioSystem_setSupportedSystemUsages},
-    {"setAllowedCapturePolicy", "(II)I", (void *)android_media_AudioSystem_setAllowedCapturePolicy},
-    {"setRttEnabled",       "(Z)I",     (void *)android_media_AudioSystem_setRttEnabled},
-    {"setAudioHalPids",  "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
-    {"isCallScreeningModeSupported", "()Z", (void *)android_media_AudioSystem_isCallScreeningModeSupported},
-    {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
-    {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
-    {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
-    {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes},
-    {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", (void *)android_media_AudioSystem_setUserIdDeviceAffinities},
-    {"removeUserIdDeviceAffinities", "(I)I", (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}
-};
+static const JNINativeMethod gMethods[] =
+        {{"setParameters", "(Ljava/lang/String;)I",
+          (void *)android_media_AudioSystem_setParameters},
+         {"getParameters", "(Ljava/lang/String;)Ljava/lang/String;",
+          (void *)android_media_AudioSystem_getParameters},
+         {"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone},
+         {"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted},
+         {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive},
+         {"isStreamActiveRemotely", "(II)Z",
+          (void *)android_media_AudioSystem_isStreamActiveRemotely},
+         {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive},
+         {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId},
+         {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId},
+         {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId},
+         {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I",
+          (void *)android_media_AudioSystem_setDeviceConnectionState},
+         {"getDeviceConnectionState", "(ILjava/lang/String;)I",
+          (void *)android_media_AudioSystem_getDeviceConnectionState},
+         {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I",
+          (void *)android_media_AudioSystem_handleDeviceConfigChange},
+         {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
+         {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse},
+         {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse},
+         {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
+         {"setStreamVolumeIndex", "(III)I", (void *)android_media_AudioSystem_setStreamVolumeIndex},
+         {"getStreamVolumeIndex", "(II)I", (void *)android_media_AudioSystem_getStreamVolumeIndex},
+         {"setVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;II)I",
+          (void *)android_media_AudioSystem_setVolumeIndexForAttributes},
+         {"getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I",
+          (void *)android_media_AudioSystem_getVolumeIndexForAttributes},
+         {"getMinVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
+          (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes},
+         {"getMaxVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
+          (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes},
+         {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume},
+         {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume},
+         {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute},
+         {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute},
+         {"setMasterMono", "(Z)I", (void *)android_media_AudioSystem_setMasterMono},
+         {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono},
+         {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance},
+         {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance},
+         {"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream},
+         {"getPrimaryOutputSamplingRate", "()I",
+          (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
+         {"getPrimaryOutputFrameCount", "()I",
+          (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
+         {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
+         {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice},
+         {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
+         {"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+          (void *)android_media_AudioSystem_listAudioPorts},
+         {"createAudioPatch",
+          "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/"
+          "AudioPortConfig;)I",
+          (void *)android_media_AudioSystem_createAudioPatch},
+         {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+          (void *)android_media_AudioSystem_releaseAudioPatch},
+         {"listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+          (void *)android_media_AudioSystem_listAudioPatches},
+         {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+          (void *)android_media_AudioSystem_setAudioPortConfig},
+         {"startAudioSource", "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
+          (void *)android_media_AudioSystem_startAudioSource},
+         {"stopAudioSource", "(I)I", (void *)android_media_AudioSystem_stopAudioSource},
+         {"getAudioHwSyncForSession", "(I)I",
+          (void *)android_media_AudioSystem_getAudioHwSyncForSession},
+         {"registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
+          (void *)android_media_AudioSystem_registerPolicyMixes},
+         {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+          (void *)android_media_AudioSystem_setUidDeviceAffinities},
+         {"removeUidDeviceAffinities", "(I)I",
+          (void *)android_media_AudioSystem_removeUidDeviceAffinities},
+         {"native_register_dynamic_policy_callback", "()V",
+          (void *)android_media_AudioSystem_registerDynPolicyCallback},
+         {"native_register_recording_callback", "()V",
+          (void *)android_media_AudioSystem_registerRecordingCallback},
+         {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
+         {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
+         {"native_is_offload_supported", "(IIIII)Z",
+          (void *)android_media_AudioSystem_isOffloadSupported},
+         {"getMicrophones", "(Ljava/util/ArrayList;)I",
+          (void *)android_media_AudioSystem_getMicrophones},
+         {"getSurroundFormats", "(Ljava/util/Map;Z)I",
+          (void *)android_media_AudioSystem_getSurroundFormats},
+         {"setSurroundFormatEnabled", "(IZ)I",
+          (void *)android_media_AudioSystem_setSurroundFormatEnabled},
+         {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid},
+         {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
+         {"isHapticPlaybackSupported", "()Z",
+          (void *)android_media_AudioSystem_isHapticPlaybackSupported},
+         {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
+          (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
+         {"setSupportedSystemUsages", "([I)I",
+          (void *)android_media_AudioSystem_setSupportedSystemUsages},
+         {"setAllowedCapturePolicy", "(II)I",
+          (void *)android_media_AudioSystem_setAllowedCapturePolicy},
+         {"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled},
+         {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
+         {"isCallScreeningModeSupported", "()Z",
+          (void *)android_media_AudioSystem_isCallScreeningModeSupported},
+         {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I",
+          (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
+         {"removePreferredDeviceForStrategy", "(I)I",
+          (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
+         {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDevice;)I",
+          (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+         {"getDevicesForAttributes",
+          "(Landroid/media/AudioAttributes;[Landroid/media/AudioDevice;)I",
+          (void *)android_media_AudioSystem_getDevicesForAttributes},
+         {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+          (void *)android_media_AudioSystem_setUserIdDeviceAffinities},
+         {"removeUserIdDeviceAffinities", "(I)I",
+          (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}};
 
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index 698062a..d41e982 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -26,6 +26,10 @@
 
 namespace android {
 
+static jboolean nativeIsEnabled(JNIEnv* env, jobject clazz) {
+    return IncFs_IsEnabled();
+}
+
 static jboolean nativeIsIncrementalPath(JNIEnv* env,
                                     jobject clazz,
                                     jstring javaPath) {
@@ -34,8 +38,8 @@
 }
 
 static const JNINativeMethod method_table[] = {
-        {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
-         (void*)nativeIsIncrementalPath},
+        {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+        {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
 };
 
 int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
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/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index e23f150..6a49220 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -178,8 +178,8 @@
     }
 
     optional Importance importance = 10;
-    optional int32 pss = 11;
-    optional int32 rss = 12;
+    optional int64 pss = 11;
+    optional int64 rss = 12;
     optional int64 timestamp = 13;
     optional string description = 14;
 }
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index a6e08d2..53aa2bb 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -708,6 +708,22 @@
 
     // ACTION: Deny "Access all files" for an app
     APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731;
+
+    // ACTION: Battery feature usage
+    ACTION_BATTERY_OPTION_FEATURE_USAGE = 1732;
+
+    // ACTION: Battery feature runtime event
+    ACTION_BATTERY_OPTION_RUNTIME_EVENT = 1733;
+
+    // ACTION: Settings > Developer Options > Toggle on Wireless debugging
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACTION_ADB_WIRELESS_ON = 1734;
+
+    // ACTION: Settings > Developer Options > Toggle off Wireless debugging
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACTION_ADB_WIRELESS_OFF = 1735;
 }
 
 /**
@@ -2562,7 +2578,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 +2589,32 @@
     // CATEGORY: SETTINGS
     // OS: R
     VIBRATE_FOR_CALLS = 1827;
+
+    // OPEN: Settings > Connected devices > Connection preferences > NFC
+    // CATEGORY: SETTINGS
+    // OS: R
+    CONNECTION_DEVICE_ADVANCED_NFC = 1828;
+
+    // OPEN: Settings -> Apps & Notifications -> Special App Access
+    INTERACT_ACROSS_PROFILES = 1829;
+
+    // OPEN: Settings > Notifications > (app or conversations) > conversation
+    NOTIFICATION_CONVERSATION_SETTINGS = 1830;
+
+    // OPEN: Settings > Developer Options > Wireless debugging
+    // CATEGORY: SETTINGS
+    // OS: R
+    SETTINGS_ADB_WIRELESS = 1831;
+
+    // OPEN: Settings > Developer Options > Wireless debugging
+    //   > Pair device with pairing code > Pairing code dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    ADB_WIRELESS_DEVICE_PAIRING_DIALOG = 1832;
+
+    // OPEN: Settings > Developer Options > Wireless debugging
+    //   > Pair device with QR code > Scan QR code > Pairing device dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833;
 }
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/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 303d62d..546c5a0 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -219,12 +219,8 @@
     // The maximum number of background jobs we allow when the system is in a
     // critical memory state.
     optional int32 bg_critical_job_count = 14;
-    // The maximum number of times we allow a job to have itself rescheduled
-    // before giving up on it, for standard jobs.
-    optional int32 max_standard_reschedule_count = 15;
-    // The maximum number of times we allow a job to have itself rescheduled
-    // before giving up on it, for jobs that are executing work.
-    optional int32 max_work_reschedule_count = 16;
+    reserved 15; // max_standard_reschedule_count
+    reserved 16; // max_work_reschedule_count
     // The minimum backoff time to allow for linear backoff.
     optional int64 min_linear_backoff_time_ms = 17;
     // The minimum backoff time to allow for exponential backoff.
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/OWNERS b/core/proto/android/service/OWNERS
new file mode 100644
index 0000000..70cb50f
--- /dev/null
+++ b/core/proto/android/service/OWNERS
@@ -0,0 +1 @@
+per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com
diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto
new file mode 100644
index 0000000..48f6670
--- /dev/null
+++ b/core/proto/android/service/sensor_service.proto
@@ -0,0 +1,248 @@
+/*
+ * 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 sint32 init_status = 16;
+    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;
+
+    // Next tag: 17
+}
+
+// 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 7a30256..ff69671 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1535,7 +1535,7 @@
          @hide
     -->
     <permission android:name="android.permission.ACCESS_CONTEXT_HUB"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|privileged" />
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB"/>
 
     <!-- @SystemApi Allows an application to create mock location providers for testing.
@@ -1998,17 +1998,17 @@
     <permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows TvInputService to access underlying TV input hardware such as
+    <!-- Allows TvInputService to access underlying TV input hardware such as
          built-in tuners and HDMI-in's.
-         @hide This should only be used by OEM's TvInputService's.
-    -->
+         <p>This should only be used by OEM's TvInputService's.
+         @hide @SystemApi -->
     <permission android:name="android.permission.TV_INPUT_HARDWARE"
         android:protectionLevel="signature|privileged|vendorPrivileged" />
 
-    <!-- @SystemApi Allows to capture a frame of TV input hardware such as
+    <!-- Allows to capture a frame of TV input hardware such as
          built-in tuners and HDMI-in's.
-         @hide <p>Not for use by third-party applications.
-    -->
+         <p>Not for use by third-party applications.
+         @hide @SystemApi -->
     <permission android:name="android.permission.CAPTURE_TV_INPUT"
         android:protectionLevel="signature|privileged" />
 
@@ -2605,7 +2605,7 @@
          <p>Not for use by third-party applications.
          @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.
@@ -3433,10 +3433,10 @@
     <permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to notify TV inputs by sending broadcasts.
+    <!-- Allows an application to notify TV inputs by sending broadcasts.
          <p>Protection level: signature|privileged
          <p>Not for use by third-party applications.
-         @hide -->
+         @hide @SystemApi -->
     <permission android:name="android.permission.NOTIFY_TV_INPUTS"
          android:protectionLevel="signature|privileged" />
 
@@ -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" />
@@ -5302,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/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 46e8f64..b01eb39 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,31 +1,50 @@
-<!--
-Copyright (C) 2020 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="240dp"
-    android:height="240dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:width="512dp"
+    android:height="512dp"
+    android:viewportWidth="512"
+    android:viewportHeight="512">
     <path
-        android:fillColor="#000"
-        android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/>
+        android:fillColor="#F86734"
+        android:pathData="M416.23 236.62h-10.67c-1.46 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.19-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65H418.9c-1.47 0-2.66-1.19-2.66-2.65v-54.4z"/>
     <path
-        android:fillColor="#000"
-        android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/>
+        android:fillColor="#F86734"
+        android:pathData="M455.51 236.62h-10.67c-1.47 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.18-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65h-10.05c-1.46 0-2.65-1.19-2.65-2.65v-54.4z"/>
     <path
-        android:fillColor="#80000000"
-        android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/>
+        android:fillColor="#D6F0FF"
+        android:pathData="M364.12 400.25a4.34 4.34 0 1 0 0 8.68a4.34 4.34 0 1 0 0-8.68z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M275.46 433.53a4.84 4.84 0 1 0 0 9.68a4.84 4.84 0 1 0 0-9.68z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M184.52 418.83a5.36 5.36 0 1 0 0 10.72a5.36 5.36 0 1 0 0-10.72z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M110.42 359.19a5.89 5.89 0 1 0 0 11.78a5.89 5.89 0 1 0 0-11.78z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M75.94 270.17a6.43 6.43 0 1 0 0 12.86a6.43 6.43 0 1 0 0-12.86z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M89.48 178.57a6.98 6.98 0 1 0 0 13.96a6.98 6.98 0 1 0 0-13.96z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M147.97 103.54a7.54 7.54 0 1 0 0 15.08a7.54 7.54 0 1 0 0-15.08z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M236.63 66.7a8.1 8.1 0 1 0 0 16.2a8.1 8.1 0 1 0 0-16.2z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M327.09 78.3a8.66 8.66 0 1 0 0 17.32a8.66 8.66 0 1 0 0-17.32z"/>
+    <path
+        android:fillColor="#D6F0FF"
+        android:pathData="M401.05 136.97a9.22 9.22 0 1 0 0 18.44a9.22 9.22 0 1 0 0-18.44z"/>
+    <group>
+        <path
+            android:fillColor="#3DDB85"
+            android:pathData="M255.45 129.46a128.11 128.11 0 1 0 0 256.22a128.11 128.11 0 1 0 0-256.22z"/>
+        <path
+            android:fillColor="#FFF"
+            android:pathData="M339.23 236.09a21.48 21.48 0 1 0 0 42.96a21.48 21.48 0 1 0 0-42.96z"/>
+    </group>
 </vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 0e9aab2..700781b 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,17 +1,17 @@
-<!--
-Copyright (C) 2020 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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
+     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,
-    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.
+     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.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
@@ -19,12 +19,22 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
-        android:fillColor="#000"
-        android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/>
-    <path
-        android:fillColor="#000"
-        android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/>
-    <path
-        android:fillColor="#80000000"
-        android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/>
+        android:fillColor="#FFFFFF"
+        android:pathData="
+M 12,1
+A 11 11 0 0 0 1,12
+A 11 11 0 1 0 12,1
+Z
+
+M 12.5,8
+a 3,3 0 0 1 6,0
+a 3,3 0 0 1 -6,0
+Z
+
+M 5.5,8
+a 3,3 0 0 1 6,0
+l 0,8
+a 3,3 0 0 1 -6,0
+Z
+"/>
 </vector>
diff --git a/core/res/res/drawable/tab_indicator_resolver.xml b/core/res/res/drawable/tab_indicator_resolver.xml
new file mode 100644
index 0000000..ff16d81a
--- /dev/null
+++ b/core/res/res/drawable/tab_indicator_resolver.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+            android:paddingMode="nest">
+    <item>
+        <ripple android:color="@color/tab_highlight_material">
+            <item android:id="@id/mask">
+                <color android:color="@color/white" />
+            </item>
+        </ripple>
+    </item>
+    <item android:gravity="bottom">
+        <shape android:shape="rectangle"
+               android:tint="@color/resolver_tabs_active_color">
+            <size android:height="2dp" />
+            <solid android:color="@color/tab_indicator_material" />
+        </shape>
+    </item>
+    <item android:bottom="2dp"
+          android:drawable="@color/transparent" />
+</layer-list>
diff --git a/core/res/res/layout/tab_indicator_resolver.xml b/core/res/res/layout/tab_indicator_resolver.xml
new file mode 100644
index 0000000..2038da6
--- /dev/null
+++ b/core/res/res/layout/tab_indicator_resolver.xml
@@ -0,0 +1,38 @@
+<?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_height="?android:attr/actionBarSize"
+    android:orientation="horizontal"
+    style="@android:style/Widget.Material.Resolver.Tab">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:visibility="gone" />
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textAllCaps="false"
+        style="@android:style/Widget.Material.TabText" />
+
+</LinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index d6c5a13..5e89533 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Skakel werkprofiel aan?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jou werkprogramme, kennisgewings, data en ander werkprofielkenmerke sal aangeskakel word"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Skakel aan"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Program is nie beskikbaar nie"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nie op die oomblik beskikbaar nie."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Hierdie program is vir \'n ouer weergawe van Android gebou en sal dalk nie behoorlik werk nie. Probeer kyk vir opdaterings, of kontak die ontwikkelaar."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index e9409fb..b637a63 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"የስራ መገለጫ ይብራ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"የእርስዎ የስራ መተግበሪያዎች፣ ማሳወቂያዎች፣ ውሂብ እና ሌሎች የስራ መገለጫ ባህሪያት ይበራሉ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"አብራ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ይህ መተግበሪያ ለቆየ የAndroid ስሪት ነው የተገነባው፣ እና በአግባቡ ላይሰራ ይችላል። ዝማኔዎች ካሉ ለመመልከት ይሞክሩ፣ ወይም ደግሞ ገንቢውን ያነጋግሩ።"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ዝማኔ ካለ አረጋግጥ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"አዲስ መልዕክቶች አለዎት"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 23ffd7b..002d61e 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -915,7 +915,7 @@
     <string name="factorytest_no_action" msgid="339252838115675515">"‏لم يتم العثور على أي حزمة توفر إجراء FACTORY_TEST."</string>
     <string name="factorytest_reboot" msgid="2050147445567257365">"إعادة تشغيل"</string>
     <string name="js_dialog_title" msgid="7464775045615023241">"تعرض الصفحة في \"<xliff:g id="TITLE">%s</xliff:g>\":"</string>
-    <string name="js_dialog_title_default" msgid="3769524569903332476">"جافا سكريبت"</string>
+    <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
     <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"تأكيد الانتقال"</string>
     <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"مغادرة هذه الصفحة"</string>
     <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"البقاء في هذه الصفحة"</string>
@@ -1395,7 +1395,7 @@
     <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"يمكنك إجراء إعادة ضبط على الإعدادات الأصلية لإيقاف وضع \"مفعِّل اختبار\"."</string>
     <string name="console_running_notification_title" msgid="6087888939261635904">"وحدة التحكّم التسلسلية مفعّلة"</string>
     <string name="console_running_notification_message" msgid="7892751888125174039">"الأداء متأثر. لإيقاف وحدة التحكّم، تحقّق من برنامج الإقلاع."</string>
-    <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"‏السوائل والشوائب في منفذ USB"</string>
+    <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"‏السوائل أو الشوائب في منفذ USB"</string>
     <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"‏تمّ إيقاف منفذ USB تلقائيًا. انقُر لمعرفة المزيد من المعلومات."</string>
     <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"‏مسموح باستخدام منفذ USB"</string>
     <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"لم يَعُد الهاتف يكتشف سوائل أو شوائب."</string>
@@ -1981,6 +1981,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"تفعيل الملف الشخصي للعمل؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"سيتم تفعيل تطبيقات العمل التي تستخدمها والإشعارات والبيانات وغيرها من ميزات الملف الشخصي للعمل"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"تشغيل"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"التطبيق غير متاح"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> غير متاح الآن."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏تمّ إنشاء هذا التطبيق لإصدار قديم من Android وقد لا يعمل بشكل صحيح. جرِّب البحث عن تحديثات أو الاتصال بمطوّر البرامج."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"البحث عن تحديث"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"لديك رسائل جديدة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index a09c377..bdf8fcf 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"কৰ্মস্থানৰ প্ৰ\'ফাইল অন কৰিবনে?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"আপোনাৰ কৰ্মস্থানৰ এপসমূহ, জাননীসমূহ, ডেটা আৰু কৰ্মস্থানৰ প্ৰ\'ফাইলৰ অইন সুবিধাসমূহ অন কৰা হ\'ব"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"অন কৰক"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"এপ্‌টো উপলব্ধ নহয়"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index b1209b1..6f13024 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili aktiv edilsin?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"İş tətbiqləri, bildirişləri, data və digər iş profili funksiyaları aktiv ediləcək"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivləşdirin"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Tətbiq əlçatan deyil"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hazırda əlçatan deyil."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu tətbiq köhnə Android versiyası üçün hazırlanıb və düzgün işləməyə bilər. Güncəlləməni yoxlayın və ya developer ilə əlaqə saxlayın."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncəlləməni yoxlayın"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 5600882..862495a 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1885,6 +1885,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Da uključimo profil za Work?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključiće se poslovne aplikacije, obaveštenja, podaci i druge funkcije profila za Work"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je napravljena za stariju verziju Android-a, pa možda neće raditi ispravno. Potražite ažuriranja ili kontaktirajte programera."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Potraži ažuriranje"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index fb80e4b..8a02da2 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Уключыць працоўны профіль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Будуць уключаны вашы рабочыя праграмы, апавяшчэнні, даныя і іншыя функцыі працоўнага профілю"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Уключыць"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Праграма недаступная"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" цяпер недаступная."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Гэта праграма была створана для больш старой версіі Android і можа не працаваць належным чынам. Праверце наяўнасць абнаўленняў або звярніцеся да распрацоўшчыка."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Праверыць на наяўнасць абнаўленняў"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас ёсць новыя паведамленні"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2892853..f7830c7 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Вкл. на служ. потр. профил?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Служебните ви приложения, известия и данни, както и другите функции на служебния потребителски профил ще бъдат включени"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Включване"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Това приложение бе създадено за по-стара версия на Android и може да не работи правилно. Опитайте да проверите за актуализации или се свържете с програмиста."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за актуализация"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови съобщения"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 4c95081..af50b33 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1297,7 +1297,7 @@
     <string name="no_permissions" msgid="5729199278862516390">"কোনো অনুমতির প্রয়োজন নেই"</string>
     <string name="perm_costs_money" msgid="749054595022779685">"এর জন্য অর্থপ্রদান করতে হতে পারে"</string>
     <string name="dlg_ok" msgid="5103447663504839312">"ঠিক আছে"</string>
-    <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB এর মাধ্যমে চার্জ করুন"</string>
+    <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB দিয়ে চার্জ করা হচ্ছে"</string>
     <string name="usb_supplying_notification_title" msgid="5378546632408101811">"সংযুক্ত ডিভাইসটি USB এর মাধ্যমে চার্জ করা হচ্ছে"</string>
     <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB ফাইল ট্রান্সফার চালু করা হয়েছে"</string>
     <string name="usb_ptp_notification_title" msgid="5043437571863443281">"USB এর মাধ্যমে PTP চালু করা হয়েছে"</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"কাজের প্রোফাইল চালু করবেন?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"আপনার কাজের অ্যাপ, বিজ্ঞপ্তি, ডেটা এবং কাজের প্রোফাইলের অন্যান্য বৈশিষ্ট্য চালু করা হবে"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"চালু করুন"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"অ্যাপ পাওয়া যাচ্ছে না"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূর্তে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ পাওয়া যাচ্ছে না।"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই অ্যাপটি Android এর একটি পুরনো ভার্সনের জন্য তৈরি করা হয়েছিল, তাই এখানে সেটি ঠিকমতো কাজ নাও করতে পারে। আপডেট পাওয়া যাচ্ছে কিনা দেখুন বা ডেভেলপারের সাথে যোগাযোগ করুন।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডেট পাওয়া যাচ্ছে কিনা দেখুন"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"আপনার নতুন মেসেজ আছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index eaa212d..d436c5f 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1887,6 +1887,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Uključiti radni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se poslovne aplikacije, obavještenja, podaci i druge funkcije radnog profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je pravljena za stariju verziju Androida i možda neće ispravno raditi. Provjerite jesu li dostupna ažuriranja ili kontaktirajte programera."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri je li dostupno ažuriranje"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 0b58cae..f3b416c 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -182,11 +182,11 @@
       <item quantity="one">Autoritat de certificació instal·lada</item>
     </plurals>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Per un tercer desconegut"</string>
-    <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil professional"</string>
+    <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil de treball"</string>
     <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Per <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
-    <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil professional"</string>
-    <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil professional o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil professional i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string>
-    <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil professional ja no està disponible en aquest dispositiu"</string>
+    <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil de treball"</string>
+    <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil de treball o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil de treball i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string>
+    <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil de treball ja no està disponible en aquest dispositiu"</string>
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has intentat introduir la contrasenya massa vegades"</string>
     <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrador ha cedit el dispositiu per a ús personal"</string>
     <string name="network_logging_notification_title" msgid="554983187553845004">"El dispositiu està gestionat"</string>
@@ -280,7 +280,7 @@
     <string name="safeMode" msgid="8974401416068943888">"Mode segur"</string>
     <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
     <string name="user_owner_label" msgid="8628726904184471211">"Canvia al perfil personal"</string>
-    <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil professional"</string>
+    <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil de treball"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contactes"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accedir als contactes"</string>
     <string name="permgrouplab_location" msgid="1858277002233964394">"Ubicació"</string>
@@ -1406,8 +1406,8 @@
     <string name="deny" msgid="6632259981847676572">"Denega"</string>
     <string name="permission_request_notification_title" msgid="1810025922441048273">"Permís sol·licitat"</string>
     <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"S\'ha sol·licitat permís\nper al compte <xliff:g id="ACCOUNT">%s</xliff:g>."</string>
-    <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil professional."</string>
-    <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil professional."</string>
+    <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil de treball."</string>
+    <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil de treball."</string>
     <string name="input_method_binding_label" msgid="1166731601721983656">"Mètode d\'introducció de text"</string>
     <string name="sync_binding_label" msgid="469249309424662147">"Sincronització"</string>
     <string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibilitat"</string>
@@ -1817,7 +1817,7 @@
     <string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"La sol·licitud SS s\'ha canviat per una videotrucada"</string>
     <string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"La sol·licitud SS s\'ha canviat per una sol·licitud USSD"</string>
     <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"S\'ha canviat a una nova sol·licitud SS"</string>
-    <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil professional"</string>
+    <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil de treball"</string>
     <string name="notification_alerted_content_description" msgid="6139691253611265992">"S\'ha enviat una alerta"</string>
     <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Desplega"</string>
     <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Replega"</string>
@@ -1850,15 +1850,17 @@
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> no està disponible en aquests moments. Aquesta opció es gestiona a <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"Més informació"</string>
     <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Reactiva l\'aplicació"</string>
-    <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil professional?"</string>
-    <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil professional"</string>
+    <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil de treball?"</string>
+    <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil de treball"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activa"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string>
     <string name="new_sms_notification_content" msgid="3197949934153460639">"Obre l\'aplicació d\'SMS per veure\'ls"</string>
     <string name="profile_encrypted_title" msgid="9001208667521266472">"Algunes funcions poden ser limitades"</string>
-    <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil professional bloquejat"</string>
+    <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil de treball bloquejat"</string>
     <string name="profile_encrypted_message" msgid="1128512616293157802">"Toca per desbloquejar el perfil"</string>
     <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"S\'ha connectat a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
     <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca per veure els fitxers"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 5874c24..754a020 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnout pracovní profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vaše pracovní aplikace, oznámení, data a ostatní funkce pracovního účtu budou zapnuty"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnout"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tato aplikace byla vytvořena pro starší verzi systému Android a nemusí fungovat správně. Zkuste vyhledat aktualizace, případně kontaktujte vývojáře."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Zkontrolovat aktualizace"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové zprávy"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index eb7ad1b..733c7f0 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Skal arbejdsprofilen slås til?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Dine arbejdsapps, notifikationer, data og andre funktioner til din arbejdsprofil deaktiveres"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå til"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgængelig"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgængelig lige nu."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne app er lavet til en ældre version af Android og fungerer muligvis ikke korrekt. Prøv at søge efter opdateringer, eller kontakt udvikleren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Søg efter opdatering"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye beskeder"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 6b01af1..9a4c606 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Arbeitsprofil aktivieren?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Deine geschäftlichen Apps, Benachrichtigungen, Daten und andere Funktionen des Arbeitsprofils werden aktiviert"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivieren"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Diese App wurde für eine ältere Android-Version entwickelt und funktioniert möglicherweise nicht mehr richtig. Prüfe, ob Updates verfügbar sind oder kontaktiere den Entwickler."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Auf Updates prüfen"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du hast neue Nachrichten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 61ca692..30a8d61 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ενεργοποίηση προφίλ εργασίας;"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Οι εφαρμογές, οι ειδοποιήσεις και τα δεδομένα εργασίας σας, καθώς και άλλες λειτουργίες του προφίλ εργασίας, θα ενεργοποιηθούν"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ενεργοποίηση"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Αυτή η εφαρμογή δημιουργήθηκε για παλαιότερη έκδοση του Android και μπορεί να μην λειτουργεί σωστά. Δοκιμάστε να ελέγξετε εάν υπάρχουν ενημερώσεις ή επικοινωνήστε με τον προγραμματιστή."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Έλεγχος για ενημέρωση"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Έχετε νέα μηνύματα"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 15a059f..d15fc5a 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index f3ab25a..bec2e34 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 15a059f..d15fc5a 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 15a059f..d15fc5a 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 871e528..42ec932 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎Turn on work profile?‎‏‎‎‏‎"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎Your work apps, notifications, data, and other work profile features will be turned on‎‏‎‎‏‎"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎Turn on‎‏‎‎‏‎"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎App is not available‎‏‎‎‏‎"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is not available right now.‎‏‎‎‏‎"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.‎‏‎‎‏‎"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎Check for update‎‏‎‎‏‎"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎You have new messages‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 1cb9fde..ac555d5 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Se activaran las apps de trabajo, los datos, las notificaciones y otras funciones del perfil de trabajo"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta app se creó para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o comunícate con el programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index d8509a3..190664b 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -802,7 +802,7 @@
     <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introduce el código PIN para desbloquear."</string>
     <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Código PIN incorrecto"</string>
     <string name="keyguard_label_text" msgid="3841953694564168384">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
-    <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Número de emergencia"</string>
+    <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Llamada de emergencia"</string>
     <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Sin servicio"</string>
     <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Pantalla bloqueada"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Tus aplicaciones, notificaciones, datos y otras funciones del perfil de trabajo se activarán"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 2148c09..0c302ee 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Kas lülitada tööprofiil sisse?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Teie töörakendused, märguanded, andmed ja muud tööprofiili funktsioonid lülitatakse sisse"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Lülita sisse"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Rakendus ei ole saadaval"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole praegu saadaval."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"See rakendus on loodud Androidi vanema versiooni jaoks ega pruugi õigesti töötada. Otsige värskendusi või võtke ühendust arendajaga."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Otsi värskendust"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Teile on uusi sõnumeid"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 07bde5e..e7aedd5 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -517,7 +517,7 @@
     <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Argazki-bilduma aldatzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_mediaLocation" msgid="7368098373378598066">"multimedia-edukien bildumako kokapena irakurri"</string>
     <string name="permdesc_mediaLocation" msgid="597912899423578138">"Multimedia-edukien bildumako kokapena irakurtzeko baimena ematen die aplikazioei."</string>
-    <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zu zarela"</string>
+    <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zeu zarela"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ez da ezagutu"</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Laneko profila aktibatu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Laneko aplikazioak, jakinarazpenak, datuak eta laneko profileko bestelako eginbideak aktibatuko dira"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktibatu"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikazioa ez dago erabilgarri"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ez dago erabilgarri une honetan."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikazioa Android-en bertsio zaharrago baterako sortu zenez, baliteke behar bezala ez funtzionatzea. Bilatu eguneratzerik baden, edo jarri garatzailearekin harremanetan."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Bilatu eguneratzeak"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Mezu berriak dituzu"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index d65357b..9d9a13c 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"نمایه کاری روشن شود؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"برنامه‌ها، اعلان‌ها، داده‌ها و سایر قابلیت‌های نمایه کاری شما روشن خواهد شد"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"روشن کردن"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"برنامه در دسترس نیست"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال‌حاضر در دسترس نیست."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏این برنامه برای نسخه قدیمی‌تری از Android ساخته شده است و ممکن است درست کار نکند. وجود به‌روزرسانی را بررسی کنید یا با برنامه‌نویس تماس بگیرید."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"بررسی وجود به‌روزرسانی"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"پیام‌های جدیدی دارید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 17b3d33b..35cc8b9 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Otetaanko työprofiili käyttöön?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Työsovellukset, ‑ilmoitukset, ‑tiedot ja muut työprofiiliominaisuudet otetaan käyttöön"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ota käyttöön"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tämä sovellus on suunniteltu vanhemmalle Android-versiolle eikä välttämättä toimi oikein. Kokeile tarkistaa päivitykset tai ottaa yhteyttä kehittäjään."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tarkista päivitykset"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Sinulle on uusia viestejä"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 45b8613..caec9fa 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activer le profil professionnel?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, vos notifications, vos données et les autres fonctionnalités de profil professionnel seront activées"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"L\'application n\'est pas accessible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas accessible pour le moment."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et pourrait ne pas fonctionner correctement. Essayez de vérifier les mises à jour ou communiquez avec son concepteur."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Vérifier la présence de mises à jour"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c646aeb..601fe43 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activer profil professionnel ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, notifications, données et d\'autres fonctionnalités de votre profil professionnel seront activées"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et risque de ne pas fonctionner correctement. Recherchez des mises à jour ou contactez le développeur."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Rechercher une mise à jour"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 2a2d7c4..0aa6277 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activar o perfil de traballo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Activaranse as túas aplicacións de traballo, as notificacións, os datos e outras funcións do perfil de traballo"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tes mensaxes novas"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index aab09f7..85bf30e 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"કાર્યાલયની પ્રોફાઇલ ચાલુ કરીએ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"તમારી કાર્યાલયની ઍપ, નોટિફિકેશન, ડેટા અને અન્ય કાર્યાલયની પ્રોફાઇલ સુવિધાઓ ચાલુ કરવામાં આવશે"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ચાલુ કરો"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ઍપ ઉપલબ્ધ નથી"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> હાલમાં ઉપલબ્ધ નથી."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"આ ઍપ Androidના જૂના વર્ઝન માટે બનાવવામાં આવ્યું હતું અને તે કદાચ તે યોગ્ય રીતે કાર્ય કરી શકશે નહીં. અપડેટ માટે તપાસવાનો પ્રયાસ કરો અથવા ડેવલપરનો સંપર્ક કરો."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"અપડેટ માટે તપાસો"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા સંદેશા છે"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index e1bb060..19e154e 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"वर्क प्रोफ़ाइल चालू करें?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"आपके काम से जुड़े ऐप्लिकेशन, सूचनाएं, डेटा और वर्क प्रोफ़ाइल से जुड़ी दूसरी सुविधाएं चालू हो जाएंगी"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करें"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> इस समय उपलब्ध नहीं है."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यह ऐप्लिकेशन Android के पुराने वर्शन के लिए बनाया गया था, इसलिए हो सकता है कि यह सही से काम न करे. देखें कि अपडेट मौजूद हैं या नहीं, या फिर डेवलपर से संपर्क करें."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"देखें कि अपडेट मौजूद है या नहीं"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"आपके पास नए संदेश हैं"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 3839434..9a9357f 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1885,6 +1885,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Želite uključiti radni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se vaše radne aplikacije, obavijesti, podaci i druge značajke radnog profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index fcea1ec..d4ac7e2 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Bekapcsolja a munkaprofilt?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"A munkahelyi alkalmazások, értesítések, adatok és a munkaprofilhoz tartozó egyéb funkciók be lesznek kapcsolva"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Bekapcsolás"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ez az alkalmazás az Android egyik korábbi verziójához készült, így elképzelhető, hogy nem működik majd megfelelően ezen a rendszeren. Keressen frissítéseket, vagy vegye fel a kapcsolatot a fejlesztővel."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Frissítés keresése"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Új üzenetei érkeztek"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index cbd754f..5cd8e8e 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Միացնե՞լ աշխատանքային պրոֆիլը"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ձեր աշխատանքային հավելվածները, ծանուցումները, տվյալները և աշխատանքային պրոֆիլի մյուս գործառույթները կմիանան"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Միացնել"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Այս հավելվածը ստեղծվել է Android-ի ավելի հին տարբերակի համար և կարող է պատշաճ չաշխատել: Ստուգեք թարմացումների առկայությունը կամ դիմեք մշակողին:"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Ստուգել նոր տարբերակի առկայությունը"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Դուք ունեք նոր հաղորդագրություններ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f1a94ff..3a1571d 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Aktifkan profil kerja?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikasi kerja, notifikasi, data, dan fitur profil kerja lainnya akan diaktifkan"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktifkan"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikasi ini dibuat untuk Android versi lama dan mungkin tidak berfungsi sebagaimana mestinya. Coba periksa apakah ada update, atau hubungi developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Periksa apakah ada update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Ada pesan baru"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index b4cb76d3..eddb214 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Kveikja á vinnusniði?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Kveikt verður á vinnuforritum, tilkynningum, gögnum og öðrum eiginleikum vinnusniðsins"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Kveikja"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Þetta forrit var hannað fyrir eldri útgáfu af Android og ekki er víst að það virki eðlilega. Athugaðu hvort uppfærslur séu í boði eða hafðu samband við þróunaraðilann."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Leita að uppfærslu"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Þú ert með ný skilaboð"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 23a62a8..135a226 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Attivare il profilo di lavoro?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Le tue app di lavoro, le notifiche, i dati e altri elementi del profilo di lavoro saranno attivati."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Attiva"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Questa app è stata realizzata per una versione precedente di Android e potrebbe non funzionare correttamente. Prova a verificare la disponibilità di aggiornamenti o contatta lo sviluppatore."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verifica la presenza di aggiornamenti"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 4319aa8..65b7384 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"להפעיל את פרופיל העבודה?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"אפליקציות העבודה, התראות, נתונים ותכונות נוספות של פרופיל העבודה יופעלו"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעל"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"האם יש עדכון חדש?"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"יש לך הודעות חדשות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 481af76..bda2064 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"仕事用プロファイルの有効化"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"仕事用のアプリ、通知、データなど、仕事用プロファイルの機能が ON になります"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ON にする"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"アプリの利用不可"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"現在 <xliff:g id="APP_NAME">%1$s</xliff:g> はご利用になれません。"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"このアプリは以前のバージョンの Android 用に作成されており、正常に動作しない可能性があります。アップデートを確認するか、デベロッパーにお問い合わせください。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"アップデートを確認"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"新着メッセージがあります"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c2c28e5..c47b710 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ჩაირთოს სამსახურის პროფილი?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"თქვენი სამსახურის აპები, შეტყობინებები, მონაცემები და სამსახურის პროფილის ყველა სხვა ფუნქცია ჩაირთვება"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ჩართვა"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"აპი მიუწვდომელია"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ამჟამად მიუწვდომელია."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ეს აპი Android-ის ძველი ვერსიისთვის შეიქმნა და შესაძლოა სათანადოდ არ მუშაობდეს. გადაამოწმეთ განახლებები ან დაუკავშირდით დეველოპერს."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"განახლების შემოწმება"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"თქვენ ახალი შეტყობინებები გაქვთ"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 7cb0512..241cf08 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Жұмыс профилі қосылсын ба?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Жұмыс қолданбалары, хабарландырулар, деректер және басқа да жұмыс профильдерінің мүмкіндіктері қосылады"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Қосу"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Қолданба қолжетімді емес"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Қолданба Android жүйесінің ескі нұсқасына арналған және дұрыс жұмыс істемеуі мүмкін. Жаңартылған нұсқаны тексеріңіз немесе әзірлеушіге хабарласыңыз."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңартылған нұсқаны тексеру"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Сізде жаңа хабарлар бар"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index e0267d3..483e236 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1855,6 +1855,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"បើក​កម្រង​ព័ត៌មាន​ការ​ងារ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"កម្មវិធី​ការងារ ការ​ជូនដំណឹង ទិន្នន័យ និង​មុខងារ​កម្រង​ព័ត៌មាន​ការងារ​ផ្សេង​ទៀត​របស់អ្នក​នឹង​ត្រូវ​បាន​បើក"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"បើក"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"មិនអាច​ប្រើ​កម្មវិធី​នេះបានទេ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"មិនអាច​ប្រើ <xliff:g id="APP_NAME">%1$s</xliff:g> នៅពេល​នេះ​បានទេ​។"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"កម្មវិធី​នេះ​ត្រូវបាន​បង្កើត​ឡើង​សម្រាប់​កំណែ​ប្រព័ន្ធ​ប្រតិបត្តិការ Android ចាស់ ហើយ​វាអាច​ដំណើរការ​ខុសប្រក្រតី។ សូម​សាកល្បង​ពិនិត្យមើល​កំណែ​ថ្មី ឬ​ទាក់ទង​ទៅអ្នក​អភិវឌ្ឍន៍។"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"រក​មើល​កំណែ​ថ្មី"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"អ្នកមានសារថ្មី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 4a2bc72..7133e89 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಆನ್ ಮಾಡುವುದೇ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ನಿಮ್ಮ ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು, ಅಧಿಸೂಚನೆಗಳು, ಡೇಟಾ ಮತ್ತು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ನ ಇತರ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗುತ್ತದೆ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ಆನ್‌ ಮಾಡಿ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು Android ನ ಹಳೆಯ ಆವೃತ್ತಿಗೆ ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡದಿರಬಹುದು. ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಅಥವಾ ಡೆವಲಪರ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ನೀವು ಹೊಸ ಸಂದೇಶಗಳನ್ನು ಹೊಂದಿರುವಿರಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 0ac7a14..12c05ba 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"직장 프로필을 사용 설정하시겠어요?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"업무용 앱, 알림, 데이터 및 기타 직장 프로필 기능이 사용 설정됩니다."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"사용 설정"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"앱을 사용할 수 없습니다"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"현재 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 사용할 수 없습니다."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"이 앱은 Android 이전 버전에 맞게 개발되었기 때문에 제대로 작동하지 않을 수 있습니다. 업데이트를 확인하거나 개발자에게 문의하세요."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"업데이트 확인"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"새 메시지 있음"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 88e36ff..ccbf845 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -190,7 +190,7 @@
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string>
     <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ түзмөктөн жеке колдонуу үчүн баш тартты"</string>
     <string name="network_logging_notification_title" msgid="554983187553845004">"Түзмөктү ишкана башкарат"</string>
-    <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын көрүү үчүн таптап коюңуз."</string>
+    <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын билгиңиз келсе, таптап коюңуз."</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Түзмөгүңүз тазаланат"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Түзмөктү башкаруучу колдонмо жараксыз. Түзмөгүңүз азыр тазаланат.\n\nСуроолоруңуз болсо, ишканаңыздын администраторуна кайрылыңыз."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Басып чыгаруу <xliff:g id="OWNER_APP">%s</xliff:g> тарабынан өчүрүлдү."</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Жумуш профили күйгүзүлсүнбү?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Жумуш колдонмолоруңуз, эскертмелериңиз, дайын-даректериңиз жана жумуш профилинин башка функциялары күйгүзүлөт."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Күйгүзүү"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Колдонмо учурда жеткиликсиз"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> учурда жеткиликсиз"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Бул колдонмо Android\'дин эски версиясы үчүн иштеп чыгарылган, андыктан туура эмес иштеши мүмкүн. Жаңыртууларды издеп көрүңүз же иштеп чыгуучуга кайрылыңыз."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңыртууну издөө"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Сизге жаңы билдирүүлөр келди"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 63b87ac..e8bda53 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ເປີດໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກບໍ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ແອັບວຽກຂອງທ່ານ, ການແຈ້ງເຕືອນ, ຂໍ້ມູນ ແລະ ຄຸນສົມບັດໂປຣໄຟລ໌ວຽກຈະຖືກເປີດໃຊ້"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ເປີດ​"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ແອັບບໍ່ສາມາດໃຊ້ໄດ້"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ແອັບນີ້ຖືກສ້າງຂຶ້ນສຳລັບ Android ເວີຊັນທີ່ເກົ່າກວ່າ ແລະ ອາດເຮັດວຽກໄດ້ບໍ່ປົກກະຕິ. ໃຫ້ລອງກວດສອບເບິ່ງອັບເດດ ຫຼື ຕິດຕໍ່ຜູ້ພັດທະນາ."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ກວດເບິ່ງອັບເດດ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ທ່ານມີຂໍ້ຄວາມໃໝ່"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index b8d848e..ad90ab3 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Įjungti darbo profilį?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Darbo programos, pranešimai, duomenys ir kitos darbo profilio funkcijos bus išjungtos"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Įjungti"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ši programa sukurta naudoti senesnės versijos sistemoje „Android“ ir gali tinkamai neveikti. Pabandykite patikrinti, ar yra naujinių, arba susisiekite su kūrėju."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tikrinti, ar yra naujinių"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Turite naujų pranešimų"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f168bb4..6ffe39a 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1885,6 +1885,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vai ieslēgt darba profilu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Tiks ieslēgtas jūsu darba lietotnes, paziņojumi, dati un citas darba profila funkcijas."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ieslēgt"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Lietotne nav pieejama"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pašlaik nav pieejama."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index f94d191..bb1d9c6 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1855,6 +1855,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Да се вклучи работниот профил?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Вашите работни апликации, известувања, податоци и други функции на работниот профил ќе бидат вклучени"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Вклучи"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Апликацијава е создадена за постара верзија на Android и може да не функционира правилно. Проверете за ажурирања или контактирајте со програмерот."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за ажурирање"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови пораки"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1efd211..31edd8c 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ഔദ്യോഗിക പ്രൊഫൈൽ ഓണാക്കണോ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾ, അറിയിപ്പുകൾ, ഡാറ്റ, മറ്റ് ഔദ്യോഗിക പ്രൊഫൈൽ ഫീച്ചറുകൾ എന്നിവ ഓണാക്കും"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ഓണാക്കുക"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ആപ്പ് ലഭ്യമല്ല"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ഈ ആപ്പ് Android-ന്റെ പഴയ പതിപ്പിനായി നിർമ്മിച്ചിരിക്കുന്നതിനാൽ ശരിയായി പ്രവർത്തിച്ചേക്കില്ല. അപ്‌ഡേറ്റിനായി പരിശോധിക്കുക, അല്ലെങ്കിൽ ഡെവലപ്പറുമായി ബന്ധപ്പെടുക."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"അപ്‌ഡേറ്റിനായി പരിശോധിക്കുക"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"നിങ്ങൾക്ക് പുതിയ സന്ദേശങ്ങൾ ഉണ്ട്"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index cc03175..eb61a46 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ажлын профайлыг асаах уу?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Таны ажлын апп, мэдэгдэл, өгөгдөл болон бусад ажлын профайлын онцлогийг асаана"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Асаах"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Апп боломжгүй байна"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> яг одоо боломжгүй байна."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Энэ аппыг Андройдын хуучин хувилбарт зориулсан бөгөөд буруу ажиллаж болзошгүй. Шинэчлэлтийг шалгаж эсвэл хөгжүүлэгчтэй холбогдоно уу."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шинэчлэлтийг шалгах"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Танд шинэ зурвасууд байна"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index cd9b6fc..0a8f4af 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल चालू ठेवायची?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"तुमची कार्य अ‍ॅप्स, सूचना, डेटा आणि अन्य कार्य प्रोफाइल वैशिष्ट्ये चालू केली जातील"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करा"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ॲप उपलब्ध नाही"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता उपलब्ध नाही."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"हे अ‍ॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अपडेट आहे का ते तपासा"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"आपल्याकडे नवीन मेसेज आहेत"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 0a40735..59c7d65 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Hidupkan profil kerja?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Apl kerja, pemberitahuan, data dan ciri profil kerja anda yang lain akan dihidupkan"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Hidupkan"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Apl tidak tersedia"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia sekarang."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Apl ini dibina untuk versi Android yang lebih lama dan mungkin tidak berfungsi dengan betul. Cuba semak kemas kini atau hubungi pembangun."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Semak kemas kini"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Anda mempunyai mesej baharu"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 9e04841..b3efcb1 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"အလုပ်ပရိုဖိုင် ဖွင့်လိုသလား။"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"သင်၏ အလုပ်အက်ပ်၊ အကြောင်းကြားချက်၊ ဒေတာနှင့် အခြားအလုပ်ပရိုဖိုင် ဝန်ဆောင်မှုများကို ဖွင့်လိုက်ပါမည်"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ဖွင့်ပါ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"အက်ပ်ကို မရနိုင်ပါ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ယခု မရနိုင်ပါ။"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ဤအက်ပ်ကို Android ဗားရှင်းဟောင်းအတွက် ပြုလုပ်ထားခြင်းဖြစ်ပြီး ပုံမှန်အလုပ်မလုပ်နိုင်ပါ။ အပ်ဒိတ်များအတွက် ရှာကြည့်ပါ သို့မဟုတ် ဆော့ဖ်ဝဲအင်ဂျင်နီယာကို ဆက်သွယ်ပါ။"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"အပ်ဒိတ်စစ်ရန်"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"သင့်ထံတွင် စာအသစ်များရောက်နေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 45ab3e5..5a3a588 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vil du slå på jobbprofilen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappene dine samt varsler, data og andre funksjoner i jobbprofilen din blir slått på"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå på"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgjengelig"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgjengelig for øyeblikket."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne appen er utviklet for en eldre versjon av Android og fungerer kanskje ikke som den skal. Prøv å se etter oppdateringer, eller kontakt utvikleren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Se etter oppdateringer"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye meldinger"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 11033a7..e95866b 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1859,6 +1859,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल सक्रिय गर्ने?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"तपाईंका कार्यसम्बन्धी अनुप्रयोग, सूचना, डेटा र कार्य प्रोफाइलका अन्य सुविधाहरू सक्रिय गरिने छन्‌"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"सक्रिय गर्नुहोस्"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"अनुप्रयोग उपलब्ध छैन"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले उपलब्ध छैन।"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यो अनुप्रयोग Android को पुरानो संस्करणका लागि बनाइएको हुनाले यसले सही ढङ्गले काम नगर्न सक्छ। अद्यावधिकहरू उपलब्ध छन् वा छैनन् भनी जाँच गरी हेर्नुहोस् वा यसको विकासकर्तालाई सम्पर्क गर्नुहोस्।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अद्यावधिक उपलब्ध छ वा छैन भनी जाँच गर्नुहोस्"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"तपाईंलाई नयाँ सन्देश आएको छ"</string>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index c5e72f0..33caeb6 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -32,4 +32,6 @@
     <color name="chooser_row_divider">@color/list_divider_color_dark</color>
     <color name="chooser_gradient_background">@color/loading_gradient_background_color_dark</color>
     <color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color>
+
+    <color name="resolver_tabs_active_color">#FF8AB4F8</color>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 13f6725..e524c1b 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Werkprofiel inschakelen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Je werk-apps, meldingen, gegevens en andere functies van je werkprofiel worden uitgeschakeld"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Inschakelen"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Deze app is ontwikkeld voor een oudere versie van Android en werkt mogelijk niet op de juiste manier. Controleer op updates of neem contact op met de ontwikkelaar."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Controleren op update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Je hebt nieuwe berichten"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 4424af7..246b4d9 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌କୁ ଚାଲୁ କରିବେ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ଆପଣଙ୍କର କାର୍ଯ୍ୟକାରୀ ଆପ୍‌, ବିଜ୍ଞପ୍ତି, ଡାଟା ଓ ଅନ୍ୟ ୱର୍କ ପ୍ରୋଫାଇଲ୍‌ଗୁଡ଼ିକ ଚାଲୁ ହୋଇଯିବ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ଅନ୍ କରନ୍ତୁ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ଏହି ଆପ୍‌କୁ Androidର ପୁରୁଣା ଭର୍ସନ୍ ପାଇଁ ନିର୍ମାଣ କରାଯାଇଥିଲା ଏବଂ ଠିକ୍ ଭାବେ କାମ କରିନପାରେ। ଏହାପାଇଁ ଅପଡେଟ୍‌ ଅଛି କି ନାହିଁ ଯାଞ୍ଚ କରନ୍ତୁ କିମ୍ବା ଡେଭେଲପର୍‌ଙ୍କ ସହିତ ସମ୍ପର୍କ କରନ୍ତୁ।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ଅପଡେଟ୍‌ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ଆପଣଙ୍କ ପାଖରେ ନୂଆ ମେସେଜ୍‍ ରହିଛି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8c87581..3d68832 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ਕੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਚਾਲੂ ਕਰਨੀ ਹੈ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ਤੁਹਾਡੀਆਂ ਕਾਰਜ-ਸਥਾਨ ਐਪਾਂ, ਸੂਚਨਾਵਾਂ, ਡਾਟਾ ਅਤੇ ਹੋਰ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ਚਾਲੂ ਕਰੋ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ਇਹ ਐਪ Android ਦੇ ਕਿਸੇ ਵਧੇਰੇ ਪੁਰਾਣੇ ਵਰਜਨ ਲਈ ਬਣਾਈ ਗਈ ਸੀ ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ। ਅੱਪਡੇਟਾਂ ਲਈ ਜਾਂਚ ਕਰੋ ਜਾਂ ਵਿਕਾਸਕਾਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ਅੱਪਡੇਟ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ਤੁਹਾਨੂੰ ਨਵੇਂ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਹੋਏ ਹਨ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5efbfbe..2fb9ac2 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Włączyć profil służbowy?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacje do pracy, powiadomienia, dane i inne funkcje profilu do pracy zostaną włączone"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Włącz"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacja jest niedostępna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest obecnie niedostępna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacja jest na starszą wersję Androida i może nie działać prawidłowo. Sprawdź dostępność aktualizacji lub skontaktuj się z programistą."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sprawdź dostępność aktualizacji"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Masz nowe wiadomości"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index db48441..1ae0c36 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -509,8 +509,8 @@
     <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string>
     <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string>
-    <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string>
-    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string>
+    <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string>
+    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string>
     <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string>
     <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string>
     <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 04296fe..c381709 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"As aplicações de trabalho, as notificações, os dados e outras funcionalidades do perfil de trabalho serão desativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicação foi concebida para uma versão mais antiga do Android e pode não funcionar corretamente. Experimente verificar se existem atualizações ou contacte o programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verificar se existem atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tem mensagens novas"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index db48441..1ae0c36 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -509,8 +509,8 @@
     <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string>
     <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string>
-    <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string>
-    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string>
+    <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string>
+    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string>
     <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string>
     <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string>
     <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string>
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 030e76a2..7c5b63e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1885,6 +1885,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activați profilul de serviciu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Se vor activa aplicațiile dvs. de serviciu, notificările, datele și alte funcții ale profilului de serviciu"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Căutați actualizări"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 94029f5..fa2c9f5 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1177,7 +1177,7 @@
     <string name="whichEditApplication" msgid="6191568491456092812">"Редактировать с помощью приложения:"</string>
     <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Редактировать с помощью приложения \"%1$s\""</string>
     <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Изменить"</string>
-    <string name="whichSendApplication" msgid="4143847974460792029">"Отправка данных"</string>
+    <string name="whichSendApplication" msgid="4143847974460792029">"Поделиться"</string>
     <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Поделиться через %1$s"</string>
     <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Поделиться"</string>
     <string name="whichSendToApplication" msgid="77101541959464018">"Выберите приложение"</string>
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Включить рабочий профиль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Будут включены корпоративные приложения, уведомления, данные и другие функции рабочего профиля."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Включить"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 73cf018..dbe1f22 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1855,6 +1855,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"කාර්යාල පැතිකඩ ක්‍රියාත්මක කරන්නද?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ඔබගේ වැඩ යෙදුම්, දැනුම්දීම්, දත්ත සහ වෙනත් කාර්යාල පැතිකඩ විශේෂාංග ක්‍රියාත්මක කරනු ඇත"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ක්‍රියාත්මක කරන්න"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"යෙදුම ලබා ගත නොහැකිය"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> මේ දැන් ලබා ගත නොහැකිය."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"මෙම යෙදුම Android හි පැරණි අනුවාදයක් සඳහා තනා ඇති අතර නිසියාකාරව ක්‍රියා නොකරනු ඇත. යාවත්කාලීන සඳහා පරික්ෂා කිරීම උත්සාහ කරන්න, නැතහොත් සංවර්ධක අමතන්න."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"යාවත්කාලීන සඳහා පරික්ෂා කරන්න"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ඔබට නව පණිවිඩ තිබේ"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index b86fc46..5483efd 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnúť pracovný profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Pracovné aplikácie, upozornenia, dáta a ďalšie funkcie pracovného profilu sa zapnú"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnúť"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikácia nie je dostupná"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> nie je teraz dostupná."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Táto aplikácia bola zostavená pre staršiu verziu Androidu a nemusí správne fungovať. Skúste skontrolovať dostupnosť aktualizácií alebo kontaktovať vývojára."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Skontrolovať dostupnosť aktualizácie"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové správy."</string>
@@ -1944,7 +1946,7 @@
     <string name="app_category_social" msgid="2278269325488344054">"Sociálne siete a komunikácia"</string>
     <string name="app_category_news" msgid="1172762719574964544">"Noviny a časopisy"</string>
     <string name="app_category_maps" msgid="6395725487922533156">"Mapy a navigácia"</string>
-    <string name="app_category_productivity" msgid="1844422703029557883">"Produktivita"</string>
+    <string name="app_category_productivity" msgid="1844422703029557883">"Kancelárske"</string>
     <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Úložisko zariadenia"</string>
     <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Ladenie cez USB"</string>
     <string name="time_picker_hour_label" msgid="4208590187662336864">"hodina"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index bc2bc3a..fed39e8 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Želite vklopiti delovni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vklopili boste svoje delovne aplikacije, obvestila, podatke in druge funkcije delovnega profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Vklop"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija ni na voljo"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno ni na voljo."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacija je bila zasnovana za starejšo različico Androida in morda ne bo delovala pravilno. Preverite, ali so na voljo posodobitve, ali pa se obrnite na razvijalca."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Preveri, ali je na voljo posodobitev"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nova sporočila."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index a353288..ec61fb9 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Të aktivizohet profili i punës?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacionet e punës, njoftimet, të dhënat e tua dhe funksionet e tjera të profilit të punës do të aktivizohen"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivizo"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacioni nuk ofrohet"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk ofrohet për momentin."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ky aplikacion është ndërtuar për një version më të vjetër të Android dhe mund të mos funksionojë mirë. Provo të kontrollosh për përditësime ose kontakto me zhvilluesin."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kontrollo për përditësim"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Ke mesazhe të reja"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 8ee644a..e1c4e18 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1885,6 +1885,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Да укључимо профил за Work?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Укључиће се пословне апликације, обавештења, подаци и друге функције профила за Work"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Укључи"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ова апликација је направљена за старију верзију Android-а, па можда неће радити исправно. Потражите ажурирања или контактирајте програмера."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Потражи ажурирање"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нове поруке"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 577933e6..0737b22 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vill du aktivera jobbprofilen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappar, aviseringar, data och andra funktioner i jobbprofilen aktiveras"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivera"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Appen har utvecklats för en äldre version av Android och kanske inte fungerar som den ska. Testa att söka efter uppdateringar eller kontakta utvecklaren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sök efter uppdateringar"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nya meddelanden"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index a6ee366..3c6b0fa 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ungependa kuwasha wasifu wa kazini?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Hatua hii itawasha data, arifa, programu za kazini, arifa na vipengele vingine vya wasifu wa kazini"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Washa"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Programu haipatikani"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> haipatikani hivi sasa."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Programu hii iliundwa kwa ajili ya toleo la zamani la Android na huenda isifanye kazi vizuri. Jaribu kuangalia masasisho au uwasiliane na msanidi programu."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Angalia masasisho"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Una ujumbe mpya"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index a56465f..e80f37b 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"பணிச் சுயவிவரத்தை ஆன் செய்யவா?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"பணி ஆப்ஸ், அறிவிப்புகள், தரவு மற்றும் பிற பணிச் சுயவிவர அம்சங்கள் ஆன் செய்யப்படும்"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"இயக்கு"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் இப்போது கிடைப்பதில்லை."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"இந்த ஆப்ஸ் Android இன் பழைய பதிப்புக்காக உருவாக்கப்பட்டதால், சரியாக வேலை செய்யாமல் போகலாம். புதுப்பிப்புகள் ஏதேனும் உள்ளதா எனப் பார்க்கவும் அல்லது டெவெலப்பரைத் தொடர்புகொள்ளவும்."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"புதுப்பிப்பு உள்ளதா எனப் பார்"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"புதிய செய்திகள் வந்துள்ளன"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 7a0aece..81d4b4f 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"కార్యాలయ ప్రొఫైల్‌ని ఆన్ చేయాలా?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"మీ కార్యాలయ యాప్‌లు, నోటిఫికేషన్‌లు, డేటా మరియు ఇతర కార్యాలయ ప్రొఫైల్ ఫీచర్‌లు ఆన్ చేయబడతాయి"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ఆన్ చేయి"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"యాప్ అందుబాటులో లేదు"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ఈ యాప్ పాత వెర్షన్ Android కోసం రూపొందించబడింది మరియు అది సరిగ్గా పని చేయకపోవచ్చు. అప్‌డేట్‌ల కోసం తనిఖీ చేయడానికి ప్రయత్నించండి లేదా డెవలపర్‌ని సంప్రదించండి."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"అప్‌డేట్ కోసం తనిఖీ చేయండి"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"మీకు కొత్త సందేశాలు ఉన్నాయి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 98f2d88..46530ca 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"เปิดโปรไฟล์งานไหม"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ระบบจะเปิดแอปงาน การแจ้งเตือน ข้อมูล และฟีเจอร์อื่นๆ ในโปรไฟล์งาน"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"เปิด"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"แอปไม่พร้อมใช้งาน"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่พร้อมใช้งานในขณะนี้"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"แอปนี้สร้างขึ้นเพื่อ Android เวอร์ชันเก่าและอาจทำงานผิดปกติ โปรดลองตรวจหาการอัปเดตหรือติดต่อนักพัฒนาซอฟต์แวร์"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ตรวจสอบอัปเดต"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"คุณมีข้อความใหม่"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c38a6d5..b9f1f29 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"I-on ang profile sa trabaho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Mao-on ang iyong mga app sa trabaho, notification, data, at iba pang feature sa profile sa trabaho"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"I-on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Hindi available ang app"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Hindi available sa ngayon ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ang app na ito ay ginawa para sa mas lumang bersyon ng Android at maaaring hindi gumana nang maayos. Subukang tingnan kung may mga update, o makipag-ugnayan sa developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tingnan kung may update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Mayroon kang mga bagong mensahe"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5e09309..81fb079 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili açılsın mı?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"İş uygulamalarınız, bildirimleriniz, verileriniz ve diğer iş profili özellikleriniz açılacak"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aç"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması şu anda kullanılamıyor."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu uygulama Android\'in daha eski bir sürümü için oluşturuldu ve düzgün çalışmayabilir. Güncellemeleri kontrol etmeyi deneyin veya geliştiriciyle iletişime geçin."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncellemeleri denetle"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 1096f97..c71ea74 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1917,6 +1917,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Увімкнути робочий профіль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Додатки, сповіщення, дані й інші функції робочого профілю буде ввімкнено"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Увімкнути"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Додаток недоступний"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Цей додаток створений для старішої версії Android і може працювати неналежним чином. Спробуйте знайти оновлення або зв’яжіться з розробником."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шукати оновлення"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас є нові повідомлення"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 4c33191..8a0425b 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"دفتری پروفائل آن کریں؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"آپ کی دفتری ایپس، اطلاعات، ڈیٹا اور دفتری پروفائل کی دیگر خصوصیات آن کر دی جائیں گی"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"آن کریں"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"ایپ دستیاب نہیں ہے"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ابھی دستیاب نہیں ہے۔"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏یہ ایپ Android کے پرانے ورژن کے لئے بنائی گئی ہے اور ہو سکتا ہے صحیح طور پر کام نہ کرے۔ اپ ڈیٹس چیک کر کے آزمائیں یا ڈیولپر سے رابطہ کریں۔"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"اپ ڈیٹ چیک کریں"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"آپ کے پاس نئے پیغامات ہیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 23873b5..b5c31c2 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ishchi profil yoqilsinmi?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ishchi ilovalar, bildirishnomalar, ma’lumotlar va boshqa ishchi profil imkoniyatlari yoqiladi"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Yoqish"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu ilova eskiroq Android versiyalariga chiqarilgan va xato ishlashi mumkin. Yangilanishlarini tekshiring yoki dasturchi bilan bog‘laning."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Yangilanish borligini tekshirish"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Sizga yangi SMS keldi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1ed30d7..970d41f 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Bạn muốn bật hồ sơ công việc?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ứng dụng công việc, thông báo, dữ liệu và các tính năng khác của hồ sơ công việc sẽ được bật"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Bật"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Ứng dụng này không dùng được"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hiện không dùng được."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ứng dụng này được xây dựng cho một phiên bản Android cũ hơn và có thể hoạt động không bình thường. Hãy thử kiểm tra các bản cập nhật hoặc liên hệ với nhà phát triển."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra bản cập nhật"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Bạn có tin nhắn mới"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 7671063..c9bba59 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要开启工作资料吗?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"您的工作应用、通知、数据及其他工作资料功能将会开启"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"开启"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"应用无法使用"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前无法使用。"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此应用专为旧版 Android 打造,因此可能无法正常运行。请尝试检查更新或与开发者联系。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"检查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新消息"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 89bea07..fc652e3 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作設定檔嗎?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟您的工作應用程式、通知、資料和其他工作設定檔功能"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"無法使用應用程式"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"目前無法使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此應用程式專為舊版 Android 打造,因此可能無法正常運作。請嘗試檢查更新,或與開發人員聯絡。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新的訊息"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index c95ae37..75d05a2 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作資料夾嗎?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟你的辦公應用程式、通知、資料和其他工作資料夾功能"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"應用程式無法使用"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前無法使用。"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"你有新訊息"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 0cd669b..0d2abc0 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1853,6 +1853,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vula iphrofayela yomsebenzi?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Izinhlelo zakho zokusebenza zomsebenzi, izaziso, idatha, nezinye izici zephrofayela yomsebenzi kuzovulwa"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Vula"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Lolu hlelo lokusebenza belakhelwe inguqulo endala ye-Android futhi kungenzeka lungasebenzi kahle. Zama ukuhlolela izibuyekezo, noma uxhumane nonjiniyela."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Hlola izibuyekezo"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Unemilayezo emisha"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b22e186..c66261b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2056,6 +2056,9 @@
         <attr name="name" />
     </declare-styleable>
     <declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" />
+    <declare-styleable name="AndroidManifestQueriesProvider" parent="AndroidManifestQueries" >
+        <attr name="authorities" />
+    </declare-styleable>
 
 
     <!-- The <code>static-library</code> tag declares that this apk is providing itself
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b2d08a9..f17cd45 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3402,8 +3402,8 @@
     <!-- True if assistant app should be pinned via Pinner Service -->
     <bool name="config_pinnerAssistantApp">false</bool>
 
-    <!-- List of files pinned by the Pinner Service with the apex boot image b/119800099 -->
-    <string-array translatable="false" name="config_apexBootImagePinnerServiceFiles">
+    <!-- List of files pinned by the Pinner Service with the JIT Zygote boot image b/119800099 -->
+    <string-array translatable="false" name="config_jitzygoteBootImagePinnerServiceFiles">
     </string-array>
 
     <!-- Number of days preloaded file cache should be preserved on a device before it can be
@@ -4343,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/symbols.xml b/core/res/res/values/symbols.xml
index c59d25f..2dd62c17 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3037,7 +3037,7 @@
   <java-symbol type="bool" name="config_pinnerCameraApp" />
   <java-symbol type="bool" name="config_pinnerHomeApp" />
   <java-symbol type="bool" name="config_pinnerAssistantApp" />
-  <java-symbol type="array" name="config_apexBootImagePinnerServiceFiles" />
+  <java-symbol type="array" name="config_jitzygoteBootImagePinnerServiceFiles" />
 
   <java-symbol type="string" name="config_doubleTouchGestureEnableFile" />
 
@@ -3863,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/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index cef21db..81ec278 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1679,6 +1679,15 @@
         <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
         <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
         <item name="navigationBarColor">@android:color/transparent</item>
+        <item name="tabWidgetStyle">@style/Widget.DeviceDefault.Resolver.TabWidget</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.Resolver.TabWidget" parent="Widget.DeviceDefault.TabWidget">
+        <item name="tabLayout">@layout/tab_indicator_resolver</item>
+    </style>
+
+    <style name="Widget.Material.Resolver.Tab" parent="Widget.Material.Tab">
+        <item name="background">@drawable/tab_indicator_resolver</item>
     </style>
 
     <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon">
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 0d7ac08..70917e7 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
          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" />
@@ -184,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" />
@@ -199,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" />
@@ -267,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..e04d3de 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",
@@ -51,9 +52,14 @@
         "android.test.base",
         "android.test.mock",
         "framework-atb-backward-compatibility",
+        "framework-all",
+        "icing-java-proto-lite",
+        "ext",
+        "framework-res",
     ],
 
     platform_apis: true,
+    sdk_version: "core_platform",
     test_suites: ["device-tests"],
 
     certificate: "platform",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ee93b39..718ca46 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" />
@@ -1326,6 +1330,12 @@
             android:process=":FakeProvider">
         </provider>
 
+        <provider
+            android:name="android.content.SlowProvider"
+            android:authorities="android.content.SlowProvider"
+            android:process=":SlowProvider">
+        </provider>
+
         <!-- Application components used for os tests -->
 
         <service android:name="android.os.MessengerService"
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index 0a21875..d1608d0 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -234,6 +234,40 @@
     }
 
     @Test
+    public void testRemoveNotificationFromWrite() {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>();
+        List<String> postRemoveExpectedStrings = new ArrayList<>();
+        for (int i = 1; i <= 10; i++) {
+            HistoricalNotification n = getHistoricalNotification("pkg", i);
+
+            if (987654323 != n.getPostedTimeMs()) {
+                postRemoveExpectedStrings.add(n.getPackage());
+                postRemoveExpectedStrings.add(n.getChannelName());
+                postRemoveExpectedStrings.add(n.getChannelId());
+                postRemoveExpectedEntries.add(n);
+            }
+
+            history.addNotificationToWrite(n);
+        }
+
+        history.poolStringsFromNotifications();
+
+        assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
+        // 1 package name and 10 unique channel names and ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(21);
+
+        history.removeNotificationFromWrite("pkg", 987654323);
+
+
+        // 1 package names and 9 * 2 unique channel names and ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(19);
+        assertThat(history.getNotificationsToWrite())
+                .containsExactlyElementsIn(postRemoveExpectedEntries);
+    }
+
+    @Test
     public void testParceling() {
         NotificationHistory history = new NotificationHistory();
 
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/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 9dcce1e..6dc7392 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -209,4 +209,13 @@
         String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote"));
         assertEquals("fake/remote", type);
     }
+
+
+    @Test
+    public void testGetType_slowProvider() {
+        // This provider is running in a different process and is intentionally slow to start.
+        // We are trying to confirm that it does not cause an ANR
+        String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider"));
+        assertEquals("slow", type);
+    }
 }
diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java
new file mode 100644
index 0000000..aba32e8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/SlowProvider.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * A dummy content provider for tests.  This provider runs in a different process from the test and
+ * is intentionally slow.
+ */
+public class SlowProvider extends ContentProvider {
+
+    private static final int ON_CREATE_LATENCY_MILLIS = 3000;
+
+    @Override
+    public boolean onCreate() {
+        try {
+            Thread.sleep(ON_CREATE_LATENCY_MILLIS);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "slow";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
index bf78203..ea69176 100644
--- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
@@ -29,6 +29,9 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.Arrays;
+import java.util.Collections;
+
 @RunWith(JUnit4.class)
 public class AtomicFormulaTest {
 
@@ -90,18 +93,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 +112,7 @@
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(
                 AtomicFormula.INSTALLER_CERTIFICATE);
-        assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCert);
+        assertThat(stringAtomicFormula.getValue()).matches(installerCert);
         assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
     }
 
@@ -230,7 +233,7 @@
     }
 
     @Test
-    public void testFormulaMatches_string_true() {
+    public void testFormulaMatches_string_packageNameFormula_true() {
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(
                         AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */
@@ -242,7 +245,7 @@
     }
 
     @Test
-    public void testFormulaMatches_string_false() {
+    public void testFormulaMatches_string_packageNameFormula_false() {
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(
                         AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */
@@ -253,6 +256,63 @@
         assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse();
     }
 
+    @Test
+    public void testFormulaMatches_string_multipleAppCertificates_true() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificates(Arrays.asList("test-cert", "cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue();
+    }
+
+    @Test
+    public void testFormulaMatches_string_multipleAppCertificates_false() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificates(Arrays.asList("test-cert", "another-cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse();
+    }
+
+    @Test
+    public void testFormulaMatches_string_multipleInstallerCertificates_true() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificates(Collections.singletonList("abc"))
+                        .setInstallerCertificates(Arrays.asList("test-cert", "cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue();
+    }
+
+    @Test
+    public void testFormulaMatches_string_multipleInstallerCertificates_false() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificates(Collections.singletonList("abc"))
+                        .setInstallerCertificates(Arrays.asList("test-cert", "another-cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse();
+    }
 
     @Test
     public void testIsAppCertificateFormula_string_true() {
@@ -430,8 +490,8 @@
     private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
-                .setAppCertificate("abc")
-                .setInstallerCertificate("abc")
+                .setAppCertificates(Collections.singletonList("abc"))
+                .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
                 .setIsPreInstalled(true);
diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
index f47dfdd..abc5fed 100644
--- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
@@ -286,8 +286,8 @@
     private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
-                .setAppCertificate("abc")
-                .setInstallerCertificate("abc")
+                .setAppCertificates(Collections.singletonList("abc"))
+                .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
                 .setIsPreInstalled(true);
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/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
new file mode 100644
index 0000000..1f831bb
--- /dev/null
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.graphics.Rect;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CutoutSpecificationTest {
+    private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
+            + "h 48\n"
+            + "v 48\n"
+            + "h -48\n"
+            + "z\n"
+            + "@left\n"
+            + "@center_vertical\n"
+            + "M 0,0\n"
+            + "h 48\n"
+            + "v 48\n"
+            + "h -48\n"
+            + "z\n"
+            + "@left\n"
+            + "@center_vertical\n"
+            + "M 0,0\n"
+            + "h -48\n"
+            + "v 48\n"
+            + "h 48\n"
+            + "z\n"
+            + "@right\n"
+            + "@dp";
+    private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
+            + "h 48\n"
+            + "v 48\n"
+            + "h -48\n"
+            + "z\n"
+            + "@left\n"
+            + "@center_vertical\n"
+            + "M 0,0\n"
+            + "h 48\n"
+            + "v 48\n"
+            + "h -48\n"
+            + "z\n"
+            + "@left\n"
+            + "@bind_left_cutout\n"
+            + "@center_vertical\n"
+            + "M 0,0\n"
+            + "h -48\n"
+            + "v 48\n"
+            + "h 48\n"
+            + "z\n"
+            + "@right\n"
+            + "@bind_right_cutout\n"
+            + "@dp";
+    private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n"
+            + "h 1\n"
+            + "v 1\n"
+            + "h -1\n"
+            + "z\n"
+            + "@left\n"
+            + "@cutout\n"
+            + "M 0, 0\n"
+            + "h -2\n"
+            + "v 2\n"
+            + "h 2\n"
+            + "z\n"
+            + "@right\n"
+            + "@bind_right_cutout\n"
+            + "@cutout\n"
+            + "M 0, 200\n"
+            + "h 3\n"
+            + "v -3\n"
+            + "h -3\n"
+            + "z\n"
+            + "@left\n"
+            + "@bind_left_cutout\n"
+            + "@bottom\n"
+            + "M 0, 0\n"
+            + "h -4\n"
+            + "v -4\n"
+            + "h 4\n"
+            + "z\n"
+            + "@right\n"
+            + "@dp";
+
+    private CutoutSpecification.Parser mParser;
+
+    /**
+     * Setup the necessary member field used by test methods.
+     */
+    @Before
+    public void setUp() {
+        mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920);
+    }
+
+    @Test
+    public void parse_nullString_shouldTriggerException() {
+        assertThrows(NullPointerException.class, () -> mParser.parse(null));
+    }
+
+    @Test
+    public void parse_emptyString_pathShouldBeNull() {
+        CutoutSpecification cutoutSpecification = mParser.parse("");
+        assertThat(cutoutSpecification.getPath()).isNull();
+    }
+
+    @Test
+    public void parse_withoutBindMarker_shouldHaveNoLeftBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getLeftBound()).isNull();
+    }
+
+    @Test
+    public void parse_withoutBindMarker_shouldHaveNoRightBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getRightBound()).isNull();
+    }
+
+    @Test
+    public void parse_withBindMarker_shouldHaveLeftBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128));
+    }
+
+    @Test
+    public void parse_withBindMarker_shouldHaveRightBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128));
+    }
+
+    @Test
+    public void parse_tallCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+                + "L -48, 0\n"
+                + "L -44.3940446283, 36.0595537175\n"
+                + "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n"
+                + "L 31.2, 48.0\n"
+                + "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n"
+                + "L 48, 0\n"
+                + "Z\n"
+                + "@dp");
+
+        assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
+    }
+
+    @Test
+    public void parse_wideCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+                + "L -72, 0\n"
+                + "L -69.9940446283, 20.0595537175\n"
+                + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+                + "L 56.8, 32.0\n"
+                + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+                + "L 72, 0\n"
+                + "Z\n"
+                + "@dp");
+
+        assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504);
+    }
+
+    @Test
+    public void parse_narrowCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+                + "L -24, 0\n"
+                + "L -21.9940446283, 20.0595537175\n"
+                + "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n"
+                + "L 8.8, 32.0\n"
+                + "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n"
+                + "L 24, 0\n"
+                + "Z\n"
+                + "@dp");
+
+        assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168);
+    }
+
+    @Test
+    public void parse_doubleCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+                + "L -72, 0\n"
+                + "L -69.9940446283, 20.0595537175\n"
+                + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+                + "L 56.8, 32.0\n"
+                + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+                + "L 72, 0\n"
+                + "Z\n"
+                + "@bottom\n"
+                + "M 0,0\n"
+                + "L -72, 0\n"
+                + "L -69.9940446283, -20.0595537175\n"
+                + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+                + "L 56.8, -32.0\n"
+                + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20"
+                + ".0595537175\n"
+                + "L 72, 0\n"
+                + "Z\n"
+                + "@dp");
+
+        assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112);
+    }
+
+    @Test
+    public void parse_cornerCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+                + "L -48, 0\n"
+                + "C -48,48 -48,48 0,48\n"
+                + "Z\n"
+                + "@dp\n"
+                + "@right");
+
+        assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
+    }
+
+    @Test
+    public void parse_holeCutout_shouldBeDone() {
+        CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n"
+                + "h 136\n"
+                + "v 136\n"
+                + "h -136\n"
+                + "Z\n"
+                + "@left");
+
+        assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0));
+    }
+
+    @Test
+    public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() {
+        CutoutSpecification cutoutSpecification =
+                new CutoutSpecification.Parser(2f, 200, 400)
+                        .parse(CORNER_CUTOUT_SPECIFICATION);
+
+        assertThat(cutoutSpecification.getSafeInset())
+                .isEqualTo(new Rect(0, 4, 0, 8));
+    }
+
+    @Test
+    public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() {
+        CutoutSpecification cutoutSpecification =
+                new CutoutSpecification.Parser(2f, 400, 200)
+                        .parse(CORNER_CUTOUT_SPECIFICATION);
+
+        assertThat(cutoutSpecification.getSafeInset())
+                .isEqualTo(new Rect(6, 0, 8, 0));
+    }
+}
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/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 12c057f..02ffc00 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -20,11 +20,11 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.UserHandle;
-import android.text.Spannable;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 
@@ -41,6 +41,7 @@
 @RunWith(AndroidJUnit4.class)
 public class EditorInfoTest {
     private static final int TEST_USER_ID = 42;
+    private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2;
 
     /**
      * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via
@@ -79,8 +80,8 @@
     }
 
     @Test
-    public void testNullTextInputComposeInitialSurroundingText() {
-        final Spannable testText = null;
+    public void setInitialText_nullInputText_throwsException() {
+        final CharSequence testText = null;
         final EditorInfo editorInfo = new EditorInfo();
 
         try {
@@ -92,56 +93,75 @@
     }
 
     @Test
-    public void testNonNullTextInputComposeInitialSurroundingText() {
-        final Spannable testText = createTestText(/* prependLength= */ 0,
-                EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
-        final EditorInfo editorInfo = new EditorInfo();
+    public void setInitialText_cursorAtHead_dividesByCursorPosition() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
 
-        // Cursor at position 0.
-        int selectionLength = 0;
+        final EditorInfo editorInfo = new EditorInfo();
+        final int selectionLength = 0;
         editorInfo.initialSelStart = 0;
         editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
-        int expectedTextBeforeCursorLength = 0;
-        int expectedTextAfterCursorLength = testText.length();
+        final int expectedTextBeforeCursorLength = 0;
+        final int expectedTextAfterCursorLength = testText.length();
 
         editorInfo.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
                 expectedTextAfterCursorLength);
+    }
 
-        // Cursor at the end.
+    @Test
+    public void setInitialText_cursorAtTail_dividesByCursorPosition() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo editorInfo = new EditorInfo();
+        final int selectionLength = 0;
         editorInfo.initialSelStart = testText.length() - selectionLength;
         editorInfo.initialSelEnd = testText.length();
-        expectedTextBeforeCursorLength = testText.length();
-        expectedTextAfterCursorLength = 0;
+        final int expectedTextBeforeCursorLength = testText.length();
+        final int expectedTextAfterCursorLength = 0;
 
         editorInfo.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
                 expectedTextAfterCursorLength);
+    }
 
-        // Cursor at the middle.
-        selectionLength = 2;
+    @Test
+    public void setInitialText_cursorAtMiddle_dividesByCursorPosition() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo editorInfo = new EditorInfo();
+        final int selectionLength = 2;
         editorInfo.initialSelStart = testText.length() / 2;
         editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
-        expectedTextBeforeCursorLength = editorInfo.initialSelStart;
-        expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+        final int expectedTextBeforeCursorLength = editorInfo.initialSelStart;
+        final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
 
         editorInfo.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
                 expectedTextAfterCursorLength);
+    }
 
-        // Accidentally swap selection start and end.
+    @Test
+    public void setInitialText_incorrectCursorOrder_correctsThenDivide() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo editorInfo = new EditorInfo();
+        final int selectionLength = 2;
         editorInfo.initialSelEnd = testText.length() / 2;
         editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
+        final int expectedTextBeforeCursorLength = testText.length() / 2;
+        final int expectedTextAfterCursorLength = testText.length() - testText.length() / 2
+                - selectionLength;
 
         editorInfo.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
                 expectedTextAfterCursorLength);
+    }
 
-        // Invalid cursor position.
+    @Test
+    public void setInitialText_invalidCursorPosition_returnsNull() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo editorInfo = new EditorInfo();
         editorInfo.initialSelStart = -1;
 
         editorInfo.setInitialSurroundingText(testText);
@@ -153,64 +173,33 @@
     }
 
     @Test
-    public void testTooLongTextInputComposeInitialSurroundingText() {
-        final Spannable testText = createTestText(/* prependLength= */ 0,
-                EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
+    public void setOverSizeInitialText_cursorAtMiddle_dividesProportionately() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
         final EditorInfo editorInfo = new EditorInfo();
-
-        // Cursor at position 0.
-        int selectionLength = 0;
-        editorInfo.initialSelStart = 0;
-        editorInfo.initialSelEnd = 0 + selectionLength;
-        int expectedTextBeforeCursorLength = 0;
-        int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
-
-        editorInfo.setInitialSurroundingText(testText);
-
-        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
-                expectedTextAfterCursorLength);
-
-        // Cursor at the end.
-        editorInfo.initialSelStart = testText.length() - selectionLength;
-        editorInfo.initialSelEnd = testText.length();
-        expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
-        expectedTextAfterCursorLength = 0;
-
-        editorInfo.setInitialSurroundingText(testText);
-
-        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
-                expectedTextAfterCursorLength);
-
-        // Cursor at the middle.
-        selectionLength = 2;
+        final int selectionLength = 2;
         editorInfo.initialSelStart = testText.length() / 2;
         editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
-        expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+        final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
                 (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength)));
-        expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH
+        final int expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH
                 - expectedTextBeforeCursorLength - selectionLength;
 
         editorInfo.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
                 expectedTextAfterCursorLength);
+    }
 
-        // Accidentally swap selection start and end.
-        editorInfo.initialSelEnd = testText.length() / 2;
-        editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
-
-        editorInfo.setInitialSurroundingText(testText);
-
-        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
-                expectedTextAfterCursorLength);
-
-        // Selection too long, selected text should be dropped.
-        selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1;
+    @Test
+    public void setOverSizeInitialText_overSizeSelection_dropsSelection() {
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
+        final EditorInfo editorInfo = new EditorInfo();
+        final int selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1;
         editorInfo.initialSelStart = testText.length() / 2;
         editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
-        expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+        final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
                 (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH));
-        expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+        final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
 
         editorInfo.setInitialSurroundingText(testText);
 
@@ -219,34 +208,59 @@
     }
 
     @Test
-    public void testTooLongSubTextInputComposeInitialSurroundingText() {
-        final int prependLength = 5;
-        final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
-        final Spannable fullText = createTestText(prependLength, subTextLength);
+    public void setInitialSubText_trimmedSubText_dividesByOriginalCursorPosition() {
+        final String prefixString = "prefix";
+        final CharSequence subText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final CharSequence originalText = TextUtils.concat(prefixString, subText);
         final EditorInfo editorInfo = new EditorInfo();
-        // Cursor at the middle.
-        final int selectionLength = 2;
-        editorInfo.initialSelStart = fullText.length() / 2;
-        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
-        // #prependLength characters will be trimmed out.
-        final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
-                editorInfo.initialSelStart - prependLength);
-        final Spannable expectedSelectedText = createExpectedText(
-                editorInfo.initialSelStart - prependLength, selectionLength);
-        final Spannable expectedTextAfterCursor = createExpectedText(
-                editorInfo.initialSelEnd - prependLength,
-                fullText.length() - editorInfo.initialSelEnd);
+        final int selLength = 2;
+        editorInfo.initialSelStart = originalText.length() / 2;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selLength;
+        final CharSequence expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
+                editorInfo.initialSelStart - prefixString.length());
+        final CharSequence expectedSelectedText = createExpectedText(
+                editorInfo.initialSelStart - prefixString.length(), selLength);
+        final CharSequence expectedTextAfterCursor = createExpectedText(
+                editorInfo.initialSelEnd - prefixString.length(),
+                originalText.length() - editorInfo.initialSelEnd);
 
-        editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength,
-                fullText.length()), prependLength);
+        editorInfo.setInitialSurroundingSubText(subText, prefixString.length());
 
         assertTrue(TextUtils.equals(expectedTextBeforeCursor,
-                editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
-                        InputConnection.GET_TEXT_WITH_STYLES)));
+                editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, anyInt())));
         assertTrue(TextUtils.equals(expectedSelectedText,
-                editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
+                editorInfo.getInitialSelectedText(anyInt())));
         assertTrue(TextUtils.equals(expectedTextAfterCursor,
-                editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, anyInt())));
+    }
+
+    @Test
+    public void initialSurroundingText_wrapIntoParcel_staysIntact() {
+        // EditorInfo.InitialSurroundingText is not visible to test class. But all its key elements
+        // must stay intact for its getter methods to return correct value and it will be wrapped
+        // into its outer class for parcel transfer, therefore we can verify its parcel
+        // wrapping/unwrapping logic through its outer class.
+        final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo sourceEditorInfo = new EditorInfo();
+        final int selectionLength = 2;
+        sourceEditorInfo.initialSelStart = testText.length() / 2;
+        sourceEditorInfo.initialSelEnd = sourceEditorInfo.initialSelStart + selectionLength;
+        sourceEditorInfo.setInitialSurroundingText(testText);
+
+        final EditorInfo targetEditorInfo = cloneViaParcel(sourceEditorInfo);
+
+        assertTrue(TextUtils.equals(
+                sourceEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES),
+                targetEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES)));
+        assertTrue(TextUtils.equals(
+                sourceEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES),
+                targetEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
+        assertTrue(TextUtils.equals(
+                sourceEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES),
+                targetEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
                         InputConnection.GET_TEXT_WITH_STYLES)));
     }
 
@@ -254,12 +268,12 @@
             @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength,
             @Nullable Integer expectAfterCursorLength) {
         final CharSequence textBeforeCursor =
-                editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
                         InputConnection.GET_TEXT_WITH_STYLES);
         final CharSequence selectedText =
                 editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
         final CharSequence textAfterCursor =
-                editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
                         InputConnection.GET_TEXT_WITH_STYLES);
 
         if (expectBeforeCursorLength == null) {
@@ -281,19 +295,15 @@
         }
     }
 
-    private static Spannable createTestText(int prependLength, int surroundingLength) {
+    private static CharSequence createTestText(int surroundingLength) {
         final SpannableStringBuilder builder = new SpannableStringBuilder();
-        for (int i = 0; i < prependLength; i++) {
-            builder.append("a");
-        }
-
         for (int i = 0; i < surroundingLength; i++) {
             builder.append(Integer.toString(i % 10));
         }
         return builder;
     }
 
-    private static Spannable createExpectedText(int startNumber, int length) {
+    private static CharSequence createExpectedText(int startNumber, int length) {
         final SpannableStringBuilder builder = new SpannableStringBuilder();
         for (int i = startNumber; i < startNumber + length; i++) {
             builder.append(Integer.toString(i % 10));
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/services/core/java/com/android/server/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
similarity index 85%
rename from services/core/java/com/android/server/GraphicsStatsService.java
rename to graphics/java/android/graphics/GraphicsStatsService.java
index 5179fa7..8dfd6ee 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package android.graphics;
 
+import android.annotation.SystemApi;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -26,13 +27,14 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.MemoryFile;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SharedMemory;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.system.ErrnoException;
 import android.util.Log;
 import android.view.IGraphicsStats;
 import android.view.IGraphicsStatsCallback;
@@ -45,6 +47,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -84,8 +87,8 @@
 
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
-    private final int ASHMEM_SIZE = nGetAshmemSize();
-    private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE];
+    private final int mAshmemSize = nGetAshmemSize();
+    private final byte[] mZeroData = new byte[mAshmemSize];
 
     private final Context mContext;
     private final AppOpsManager mAppOps;
@@ -97,6 +100,7 @@
     private Handler mWriteOutHandler;
     private boolean mRotateIsScheduled = false;
 
+    @SystemApi
     public GraphicsStatsService(Context context) {
         mContext = context;
         mAppOps = context.getSystemService(AppOpsManager.class);
@@ -108,7 +112,8 @@
             throw new IllegalStateException("Graphics stats directory does not exist: "
                     + mGraphicsStatsDir.getAbsolutePath());
         }
-        HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND);
+        HandlerThread bgthread = new HandlerThread("GraphicsStats-disk",
+                Process.THREAD_PRIORITY_BACKGROUND);
         bgthread.start();
 
         mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() {
@@ -159,7 +164,7 @@
                 active.mCallback.onRotateGraphicsStatsBuffer();
             } catch (RemoteException e) {
                 Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers",
-                        active.mInfo.packageName, active.mPid), e);
+                        active.mInfo.mPackageName, active.mPid), e);
             }
         }
         // Give a few seconds for everyone to rotate before doing the cleanup
@@ -167,8 +172,8 @@
     }
 
     @Override
-    public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token)
-            throws RemoteException {
+    public ParcelFileDescriptor requestBufferForProcess(String packageName,
+            IGraphicsStatsCallback token) throws RemoteException {
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
         ParcelFileDescriptor pfd = null;
@@ -196,7 +201,7 @@
     // current day.
     // This method is invoked from native code only.
     @SuppressWarnings({"UnusedDeclaration"})
-    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+    private void pullGraphicsStats(boolean lastFullDay, long pulledData) throws RemoteException {
         int uid = Binder.getCallingUid();
 
         // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
@@ -213,13 +218,13 @@
 
         long callingIdentity = Binder.clearCallingIdentity();
         try {
-            return pullGraphicsStatsImpl(lastFullDay);
+            pullGraphicsStatsImpl(lastFullDay, pulledData);
         } finally {
             Binder.restoreCallingIdentity(callingIdentity);
         }
     }
 
-    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+    private void pullGraphicsStatsImpl(boolean lastFullDay, long pulledData) {
         long targetDay;
         if (lastFullDay) {
             // Get stats from yesterday. Stats stay constant, because the day is over.
@@ -235,7 +240,7 @@
             buffers = new ArrayList<>(mActive.size());
             for (int i = 0; i < mActive.size(); i++) {
                 ActiveBuffer buffer = mActive.get(i);
-                if (buffer.mInfo.startTime == targetDay) {
+                if (buffer.mInfo.mStartTime == targetDay) {
                     try {
                         buffers.add(new HistoricalBuffer(buffer));
                     } catch (IOException ex) {
@@ -267,18 +272,7 @@
                 }
             }
         } finally {
-            return nFinishDumpInMemory(dump);
-        }
-    }
-
-    private ParcelFileDescriptor getPfd(MemoryFile file) {
-        try {
-            if (!file.getFileDescriptor().valid()) {
-                throw new IllegalStateException("Invalid file descriptor");
-            }
-            return ParcelFileDescriptor.dup(file.getFileDescriptor());
-        } catch (IOException ex) {
-            throw new IllegalStateException("Failed to get PFD from memory file", ex);
+            nFinishDumpInMemory(dump, pulledData, lastFullDay);
         }
     }
 
@@ -286,7 +280,7 @@
             int uid, int pid, String packageName, long versionCode) throws RemoteException {
         ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
         scheduleRotateLocked();
-        return getPfd(buffer.mProcessBuffer);
+        return buffer.getPfd();
     }
 
     private Calendar normalizeDate(long timestamp) {
@@ -301,13 +295,15 @@
 
     private File pathForApp(BufferInfo info) {
         String subPath = String.format("%d/%s/%d/total",
-                normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode);
+                normalizeDate(info.mStartTime).getTimeInMillis(), info.mPackageName,
+                info.mVersionCode);
         return new File(mGraphicsStatsDir, subPath);
     }
 
     private void saveBuffer(HistoricalBuffer buffer) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName);
+            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+                    "saving graphicsstats for " + buffer.mInfo.mPackageName);
         }
         synchronized (mFileAccessLock) {
             File path = pathForApp(buffer.mInfo);
@@ -317,8 +313,9 @@
                 Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
                 return;
             }
-            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode,
-                    buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData);
+            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName,
+                    buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
+                    buffer.mData);
         }
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
@@ -365,7 +362,7 @@
             HistoricalBuffer data = new HistoricalBuffer(buffer);
             Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
         } catch (IOException e) {
-            Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e);
+            Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.mPackageName, e);
         }
         buffer.closeAllBuffers();
     }
@@ -386,7 +383,7 @@
             if (buffer.mPid == pid
                     && buffer.mUid == uid) {
                 // If the buffer is too old we remove it and return a new one
-                if (buffer.mInfo.startTime < today) {
+                if (buffer.mInfo.mStartTime < today) {
                     buffer.binderDied();
                     break;
                 } else {
@@ -410,8 +407,8 @@
             HistoricalBuffer buffer = buffers.get(i);
             File path = pathForApp(buffer.mInfo);
             skipFiles.add(path);
-            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,
-                    buffer.mInfo.versionCode,  buffer.mInfo.startTime, buffer.mInfo.endTime,
+            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName,
+                    buffer.mInfo.mVersionCode,  buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
                     buffer.mData);
         }
         return skipFiles;
@@ -478,20 +475,20 @@
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
-    private static native long nFinishDumpInMemory(long dump);
+    private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
     private final class BufferInfo {
-        final String packageName;
-        final long versionCode;
-        long startTime;
-        long endTime;
+        final String mPackageName;
+        final long mVersionCode;
+        long mStartTime;
+        long mEndTime;
 
         BufferInfo(String packageName, long versionCode, long startTime) {
-            this.packageName = packageName;
-            this.versionCode = versionCode;
-            this.startTime = startTime;
+            this.mPackageName = packageName;
+            this.mVersionCode = versionCode;
+            this.mStartTime = startTime;
         }
     }
 
@@ -501,7 +498,8 @@
         final int mPid;
         final IGraphicsStatsCallback mCallback;
         final IBinder mToken;
-        MemoryFile mProcessBuffer;
+        SharedMemory mProcessBuffer;
+        ByteBuffer mMapping;
 
         ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
                 long versionCode)
@@ -512,8 +510,14 @@
             mCallback = token;
             mToken = mCallback.asBinder();
             mToken.linkToDeath(this, 0);
-            mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);
-            mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);
+            try {
+                mProcessBuffer = SharedMemory.create("GFXStats-" + pid, mAshmemSize);
+                mMapping = mProcessBuffer.mapReadWrite();
+            } catch (ErrnoException ex) {
+                ex.rethrowAsIOException();
+            }
+            mMapping.position(0);
+            mMapping.put(mZeroData, 0, mAshmemSize);
         }
 
         @Override
@@ -523,20 +527,40 @@
         }
 
         void closeAllBuffers() {
+            if (mMapping != null) {
+                SharedMemory.unmap(mMapping);
+                mMapping = null;
+            }
             if (mProcessBuffer != null) {
                 mProcessBuffer.close();
                 mProcessBuffer = null;
             }
         }
+
+        ParcelFileDescriptor getPfd() {
+            try {
+                return mProcessBuffer.getFdDup();
+            } catch (IOException ex) {
+                throw new IllegalStateException("Failed to get PFD from memory file", ex);
+            }
+        }
+
+        void readBytes(byte[] buffer, int count) throws IOException  {
+            if (mMapping == null) {
+                throw new IOException("SharedMemory has been deactivated");
+            }
+            mMapping.position(0);
+            mMapping.get(buffer, 0, count);
+        }
     }
 
     private final class HistoricalBuffer {
         final BufferInfo mInfo;
-        final byte[] mData = new byte[ASHMEM_SIZE];
+        final byte[] mData = new byte[mAshmemSize];
         HistoricalBuffer(ActiveBuffer active) throws IOException {
             mInfo = active.mInfo;
-            mInfo.endTime = System.currentTimeMillis();
-            active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);
+            mInfo.mEndTime = System.currentTimeMillis();
+            active.readBytes(mData, mAshmemSize);
         }
     }
 }
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/Android.bp b/libs/hwui/Android.bp
index 51270f5..debb38b2 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -92,9 +92,12 @@
                 "libandroidfw",
                 "libcrypto",
                 "libsync",
+                "libstatspull",
+                "libstatssocket",
             ],
             static_libs: [
                 "libEGL_blobCache",
+                "libprotoutil",
             ],
         },
         host: {
@@ -168,7 +171,6 @@
         "renderthread/RenderTask.cpp",
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
-        "hwui/AnimatedImageThread.cpp",
         "hwui/Bitmap.cpp",
         "hwui/Canvas.cpp",
         "hwui/ImageDecoder.cpp",
@@ -210,6 +212,7 @@
         android: {
 
             srcs: [
+                "hwui/AnimatedImageThread.cpp",
                 "pipeline/skia/ATraceMemoryDump.cpp",
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
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/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 6761435..31e4555 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -27,7 +27,6 @@
 #include "DamageAccumulator.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #endif
-#include "utils/FatVector.h"
 #include "utils/MathUtils.h"
 #include "utils/StringUtils.h"
 #include "utils/TraceUtils.h"
@@ -37,6 +36,7 @@
 #include <atomic>
 #include <sstream>
 #include <string>
+#include <ui/FatVector.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index d55e5b0..c0ec217 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -27,6 +27,8 @@
 
 #include <androidfw/ResourceTypes.h>
 
+#include <ui/FatVector.h>
+
 #include "AnimatorManager.h"
 #include "CanvasTransform.h"
 #include "Debug.h"
@@ -35,7 +37,6 @@
 #include "RenderProperties.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaLayer.h"
-#include "utils/FatVector.h"
 
 #include <vector>
 
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 4544bea..638de85 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -15,7 +15,9 @@
  */
 
 #include "AnimatedImageDrawable.h"
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
 #include "AnimatedImageThread.h"
+#endif
 
 #include "utils/TraceUtils.h"
 
@@ -160,8 +162,10 @@
     } else if (starting) {
         // The image has animated, and now is being reset. Queue up the first
         // frame, but keep showing the current frame until the first is ready.
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
         auto& thread = uirenderer::AnimatedImageThread::getInstance();
         mNextSnapshot = thread.reset(sk_ref_sp(this));
+#endif
     }
 
     bool finalFrame = false;
@@ -187,8 +191,10 @@
     }
 
     if (mRunning && !mNextSnapshot.valid()) {
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
         auto& thread = uirenderer::AnimatedImageThread::getInstance();
         mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
+#endif
     }
 
     if (!drawDirectly) {
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 4b2857f..afd82ac 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -24,6 +24,19 @@
 
 using namespace android;
 
+sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
+    const skcms_ICCProfile* encodedProfile = mCodec->getICCProfile();
+    if (encodedProfile) {
+        // If the profile maps directly to an SkColorSpace, that SkColorSpace
+        // will be returned. Otherwise, nullptr will be returned. In either
+        // case, using this SkColorSpace results in doing no color correction.
+        return SkColorSpace::Make(*encodedProfile);
+    }
+
+    // The image has no embedded color profile, and should be treated as SRGB.
+    return SkColorSpace::MakeSRGB();
+}
+
 ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
     : mCodec(std::move(codec))
     , mPeeker(std::move(peeker))
@@ -31,7 +44,7 @@
     , mDecodeSize(mTargetSize)
     , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType))
     , mUnpremultipliedRequired(false)
-    , mOutColorSpace(mCodec->computeOutputColorSpace(mOutColorType, nullptr))
+    , mOutColorSpace(getDefaultColorSpace())
     , mSampleSize(1)
 {
 }
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 0c99f84..a1b5157 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -43,6 +43,7 @@
 
     bool setUnpremultipliedRequired(bool unpremultipliedRequired);
 
+    sk_sp<SkColorSpace> getDefaultColorSpace() const;
     void setOutColorSpace(sk_sp<SkColorSpace> cs);
 
     // The size is the final size after scaling and cropping.
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
index cfc0f9b..d669f84 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
@@ -21,7 +21,7 @@
 
 #include <SkCanvas.h>
 #include <SkDrawable.h>
-#include <utils/FatVector.h>
+#include <ui/FatVector.h>
 
 namespace android {
 namespace uirenderer {
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/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index cae3e3b..206b58f 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -27,7 +27,6 @@
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaVulkanPipeline.h"
 #include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
 #include "utils/TimeUtils.h"
 #include "utils/TraceUtils.h"
 
@@ -40,6 +39,8 @@
 #include <utils/Mutex.h>
 #include <thread>
 
+#include <ui/FatVector.h>
+
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index a5355fc..ba70afc 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -23,13 +23,13 @@
 #include <GrContext.h>
 #include <GrTypes.h>
 #include <android/sync.h>
+#include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
 #include "Properties.h"
 #include "RenderThread.h"
 #include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
 #include "utils/TraceUtils.h"
 
 namespace android {
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index c418617..644d5fb 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -26,9 +26,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <algorithm>
-#include <map>
-#include <vector>
+#include <android/util/ProtoOutputStream.h>
+#include <stats_event.h>
+#include <statslog.h>
 
 #include "JankTracker.h"
 #include "protos/graphicsstats.pb.h"
@@ -61,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -485,79 +485,82 @@
     delete dump;
 }
 
-class MemOutputStreamLite : public io::ZeroCopyOutputStream {
-public:
-    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
-    virtual ~MemOutputStreamLite() {}
+using namespace google::protobuf;
 
-    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
 
-    virtual void BackUp(int count) override { mImpl.BackUp(count); }
-
-    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
-
-    bool Flush() { return mImpl.Flush(); }
-
-    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
-        int bufferOffset = 0;
-        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
-        int totalDataLeft = totalSize;
-        for (auto& it : mCopyAdapter.mBuffers) {
-            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
-            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
-            bufferOffset += bufferSize;
-            totalDataLeft -= bufferSize;
-        }
+static void writeCpuHistogram(AStatsEvent* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
     }
-
-private:
-    struct MemAdapter : public io::CopyingOutputStream {
-        // Data is stored in an array of buffers.
-        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
-        std::vector<std::vector<unsigned char>> mBuffers;
-        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
-        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
-        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
-
-        explicit MemAdapter() {}
-        virtual ~MemAdapter() {}
-
-        virtual bool Write(const void* buffer, int size) override {
-            while (size > 0) {
-                if (0 == mCurrentBufferUnusedSize) {
-                    mCurrentBufferUnusedSize =
-                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
-                    mBuffers.emplace_back();
-                    mBuffers.back().resize(mCurrentBufferUnusedSize);
-                    mCurrentBuffer = mBuffers.back().data();
-                    mBuffersSize += mCurrentBufferUnusedSize;
-                }
-                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
-                memcpy(mCurrentBuffer, buffer, dataMoved);
-                mCurrentBufferUnusedSize -= dataMoved;
-                mCurrentBuffer += dataMoved;
-                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
-                size -= dataMoved;
-            }
-            return true;
-        }
-    };
-
-    MemOutputStreamLite::MemAdapter mCopyAdapter;
-    io::CopyingOutputStreamAdaptor mImpl;
-};
-
-void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
-                                              void* param2) {
-    MemOutputStreamLite stream;
-    dump->updateProto();
-    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
-    delete dump;
-    if (!success) {
-        return;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
     }
-    stream.copyData(reader, param1, param2);
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
 }
 
+static void writeGpuHistogram(AStatsEvent* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
+}
+
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data,
+                                              bool lastFullDay) {
+    dump->updateProto();
+    auto& serviceDump = dump->proto();
+    for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+        auto& stat = serviceDump.stats(stat_index);
+        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.
+        AStatsEvent_writeInt64(event, (int64_t)0);
+        AStatsEvent_writeBool(event, !lastFullDay);
+        AStatsEvent_build(event);
+    }
+}
+
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 4bed9633..59e21d0 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -20,6 +20,7 @@
 
 #include "JankTracker.h"
 #include "utils/Macros.h"
+#include <stats_pull_atom_callback.h>
 
 namespace android {
 namespace uirenderer {
@@ -27,9 +28,6 @@
 class GraphicsStatsProto;
 }
 
-typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
-                             void* param1, void* param2);
-
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -56,8 +54,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
-    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
-                                               void* param2);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, AStatsEventList* data,
+                                               bool lastFullDay);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/unit/FatVectorTests.cpp b/libs/hwui/tests/unit/FatVectorTests.cpp
index 8523e6c..6585a62 100644
--- a/libs/hwui/tests/unit/FatVectorTests.cpp
+++ b/libs/hwui/tests/unit/FatVectorTests.cpp
@@ -15,7 +15,7 @@
  */
 
 #include <gtest/gtest.h>
-#include <utils/FatVector.h>
+#include <ui/FatVector.h>
 
 #include <tests/common/TestUtils.h>
 
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/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
deleted file mode 100644
index 8cc4d10..0000000
--- a/libs/hwui/utils/FatVector.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2015, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef ANDROID_FAT_VECTOR_H
-#define ANDROID_FAT_VECTOR_H
-
-#include "utils/Macros.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <utils/Log.h>
-#include <type_traits>
-
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-template <typename T, size_t SIZE>
-class InlineStdAllocator {
-public:
-    struct Allocation {
-        PREVENT_COPY_AND_ASSIGN(Allocation);
-
-    public:
-        Allocation(){};
-        // char array instead of T array, so memory is uninitialized, with no destructors run
-        char array[sizeof(T) * SIZE];
-        bool inUse = false;
-    };
-
-    typedef T value_type;  // needed to implement std::allocator
-    typedef T* pointer;    // needed to implement std::allocator
-
-    explicit InlineStdAllocator(Allocation& allocation) : mAllocation(allocation) {}
-    InlineStdAllocator(const InlineStdAllocator& other) : mAllocation(other.mAllocation) {}
-    ~InlineStdAllocator() {}
-
-    T* allocate(size_t num, const void* = 0) {
-        if (!mAllocation.inUse && num <= SIZE) {
-            mAllocation.inUse = true;
-            return (T*)mAllocation.array;
-        } else {
-            return (T*)malloc(num * sizeof(T));
-        }
-    }
-
-    void deallocate(pointer p, size_t num) {
-        if (p == (T*)mAllocation.array) {
-            mAllocation.inUse = false;
-        } else {
-            // 'free' instead of delete here - destruction handled separately
-            free(p);
-        }
-    }
-    Allocation& mAllocation;
-};
-
-/**
- * std::vector with SIZE elements preallocated into an internal buffer.
- *
- * Useful for avoiding the cost of malloc in cases where only SIZE or
- * fewer elements are needed in the common case.
- */
-template <typename T, size_t SIZE>
-class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> {
-public:
-    FatVector()
-            : std::vector<T, InlineStdAllocator<T, SIZE>>(
-                      InlineStdAllocator<T, SIZE>(mAllocation)) {
-        this->reserve(SIZE);
-    }
-
-    explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); }
-
-private:
-    typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
-};
-
-}  // namespace uirenderer
-}  // namespace android
-
-#endif  // ANDROID_FAT_VECTOR_H
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/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index e4348f2..3b494e9 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -251,19 +251,24 @@
 void PointerController::setPresentation(Presentation presentation) {
     AutoMutex _l(mLock);
 
-    if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) {
-        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
-                &mLocked.animationResources, mLocked.viewport.displayId);
+    if (mLocked.presentation == presentation) {
+        return;
     }
 
-    if (mLocked.presentation != presentation) {
-        mLocked.presentation = presentation;
-        mLocked.presentationChanged = true;
+    mLocked.presentation = presentation;
+    mLocked.presentationChanged = true;
 
-        if (presentation != PRESENTATION_SPOT) {
-            fadeOutAndReleaseAllSpotsLocked();
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+
+    if (presentation == PRESENTATION_POINTER) {
+        if (mLocked.additionalMouseResources.empty()) {
+            mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                                  &mLocked.animationResources,
+                                                  mLocked.viewport.displayId);
         }
-
+        fadeOutAndReleaseAllSpotsLocked();
         updatePointerLocked();
     }
 }
@@ -285,6 +290,9 @@
 #endif
 
     AutoMutex _l(mLock);
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
 
     std::vector<Spot*> newSpots;
     std::map<int32_t, std::vector<Spot*>>::const_iterator iter =
@@ -331,6 +339,9 @@
 #endif
 
     AutoMutex _l(mLock);
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
 
     fadeOutAndReleaseAllSpotsLocked();
 }
@@ -752,6 +763,10 @@
 }
 
 void PointerController::loadResourcesLocked() REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+
     mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId);
     mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
 
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index b36406d..a157426 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -39,8 +39,8 @@
 
 using ::testing::AllOf;
 using ::testing::Field;
-using ::testing::NiceMock;
 using ::testing::Mock;
+using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::Test;
 
@@ -57,12 +57,20 @@
     virtual int32_t getDefaultPointerIconId() override;
     virtual int32_t getCustomPointerIconId() override;
 
+    bool allResourcesAreLoaded();
+    bool noResourcesAreLoaded();
+
 private:
     void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
+
+    bool pointerIconLoaded{false};
+    bool pointerResourcesLoaded{false};
+    bool additionalMouseResourcesLoaded{false};
 };
 
 void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
     loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
+    pointerIconLoaded = true;
 }
 
 void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
@@ -70,6 +78,7 @@
     loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
     loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
     loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
+    pointerResourcesLoaded = true;
 }
 
 void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
@@ -91,6 +100,8 @@
     anim.durationPerFrame = 10;
     (*outResources)[cursorType] = icon;
     (*outAnimationResources)[cursorType] = anim;
+
+    additionalMouseResourcesLoaded = true;
 }
 
 int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
@@ -101,18 +112,27 @@
     return CURSOR_TYPE_CUSTOM;
 }
 
+bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() {
+    return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded;
+}
+
+bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() {
+    return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded);
+}
+
 void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) {
     icon->style = type;
     std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type);
     icon->hotSpotX = hotSpot.first;
     icon->hotSpotY = hotSpot.second;
 }
-
 class PointerControllerTest : public Test {
 protected:
     PointerControllerTest();
     ~PointerControllerTest();
 
+    void ensureDisplayViewportIsSet();
+
     sp<MockSprite> mPointerSprite;
     sp<MockPointerControllerPolicyInterface> mPolicy;
     sp<MockSpriteController> mSpriteController;
@@ -141,7 +161,14 @@
             .WillOnce(Return(mPointerSprite));
 
     mPointerController = new PointerController(mPolicy, mLooper, mSpriteController);
+}
 
+PointerControllerTest::~PointerControllerTest() {
+    mRunning.store(false, std::memory_order_relaxed);
+    mThread.join();
+}
+
+void PointerControllerTest::ensureDisplayViewportIsSet() {
     DisplayViewport viewport;
     viewport.displayId = ADISPLAY_ID_DEFAULT;
     viewport.logicalRight = 1600;
@@ -151,11 +178,9 @@
     viewport.deviceWidth = 400;
     viewport.deviceHeight = 300;
     mPointerController->setDisplayViewport(viewport);
-}
 
-PointerControllerTest::~PointerControllerTest() {
-    mRunning.store(false, std::memory_order_relaxed);
-    mThread.join();
+    // The first call to setDisplayViewport should trigger the loading of the necessary resources.
+    EXPECT_TRUE(mPolicy->allResourcesAreLoaded());
 }
 
 void PointerControllerTest::loopThread() {
@@ -167,6 +192,7 @@
 }
 
 TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
+    ensureDisplayViewportIsSet();
     mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
 
     std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT);
@@ -181,6 +207,7 @@
 }
 
 TEST_F(PointerControllerTest, updatePointerIcon) {
+    ensureDisplayViewportIsSet();
     mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
 
     int32_t type = CURSOR_TYPE_ADDITIONAL;
@@ -196,6 +223,7 @@
 }
 
 TEST_F(PointerControllerTest, setCustomPointerIcon) {
+    ensureDisplayViewportIsSet();
     mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
 
     int32_t style = CURSOR_TYPE_CUSTOM;
@@ -217,4 +245,18 @@
     mPointerController->setCustomPointerIcon(icon);
 }
 
+TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
+    mPointerController->setPresentation(PointerController::PRESENTATION_POINTER);
+    mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1);
+    mPointerController->clearSpots();
+    mPointerController->setPosition(1.0f, 1.0f);
+    mPointerController->move(1.0f, 1.0f);
+    mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
+    mPointerController->fade(PointerController::TRANSITION_IMMEDIATE);
+
+    EXPECT_TRUE(mPolicy->noResourcesAreLoaded());
+
+    ensureDisplayViewportIsSet();
+}
+
 }  // namespace android
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index 9b047ca..1e62107 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -20,7 +20,6 @@
         ":IDropBoxManagerService.aidl",
         "src/content/ComponentName.cpp",
         "src/os/DropBoxManager.cpp",
-        "src/os/StatsDimensionsValue.cpp",
     ],
 
     shared_libs: [
diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h
deleted file mode 100644
index cc0b056..0000000
--- a/libs/services/include/android/os/StatsDimensionsValue.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-#ifndef STATS_DIMENSIONS_VALUE_H
-#define STATS_DIMENSIONS_VALUE_H
-
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
-#include <binder/Status.h>
-#include <utils/String16.h>
-#include <vector>
-
-namespace android {
-namespace os {
-
-// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java.
-class StatsDimensionsValue : public android::Parcelable {
-public:
-    StatsDimensionsValue();
-
-    StatsDimensionsValue(int32_t field, String16 value);
-    StatsDimensionsValue(int32_t field, int32_t value);
-    StatsDimensionsValue(int32_t field, int64_t value);
-    StatsDimensionsValue(int32_t field, bool value);
-    StatsDimensionsValue(int32_t field, float value);
-    StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value);
-
-    virtual ~StatsDimensionsValue();
-
-    virtual android::status_t writeToParcel(android::Parcel* out) const override;
-    virtual android::status_t readFromParcel(const android::Parcel* in) override;
-
-private:
-    // Keep constants in sync with android/os/StatsDimensionsValue.java
-    // and stats_log.proto's DimensionValue.
-    static const int kStrValueType = 2;
-    static const int kIntValueType = 3;
-    static const int kLongValueType = 4;
-    static const int kBoolValueType = 5;
-    static const int kFloatValueType = 6;
-    static const int kTupleValueType = 7;
-
-    int32_t mField;
-    int32_t mValueType;
-
-    // This isn't very clever, but it isn't used for long-term storage, so it'll do.
-    String16 mStrValue;
-    int32_t mIntValue;
-    int64_t mLongValue;
-    bool mBoolValue;
-    float mFloatValue;
-    std::vector<StatsDimensionsValue> mTupleValue;
-};
-
-}  // namespace os
-}  // namespace android
-
-#endif // STATS_DIMENSIONS_VALUE_H
diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp
deleted file mode 100644
index 0052e0b..0000000
--- a/libs/services/src/os/StatsDimensionsValue.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "StatsDimensionsValue"
-
-#include "android/os/StatsDimensionsValue.h"
-
-#include <cutils/log.h>
-
-using android::Parcel;
-using android::Parcelable;
-using android::status_t;
-using std::vector;
-
-namespace android {
-namespace os {
-
-StatsDimensionsValue::StatsDimensionsValue() {};
-
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) :
-    mField(field),
-    mValueType(kStrValueType),
-    mStrValue(value) {
-}
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) :
-    mField(field),
-    mValueType(kIntValueType),
-    mIntValue(value) {
-}
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) :
-    mField(field),
-    mValueType(kLongValueType),
-    mLongValue(value) {
-}
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) :
-    mField(field),
-    mValueType(kBoolValueType),
-    mBoolValue(value) {
-}
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) :
-    mField(field),
-    mValueType(kFloatValueType),
-    mFloatValue(value) {
-}
-StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) :
-    mField(field),
-    mValueType(kTupleValueType),
-    mTupleValue(value) {
-}
-
-StatsDimensionsValue::~StatsDimensionsValue() {}
-
-status_t
-StatsDimensionsValue::writeToParcel(Parcel* out) const {
-    status_t err ;
-
-    err = out->writeInt32(mField);
-    if (err != NO_ERROR) {
-        return err;
-    }
-    err = out->writeInt32(mValueType);
-    if (err != NO_ERROR) {
-        return err;
-    }
-    switch (mValueType) {
-        case kStrValueType:
-            err = out->writeString16(mStrValue);
-            break;
-        case kIntValueType:
-            err = out->writeInt32(mIntValue);
-            break;
-        case kLongValueType:
-            err = out->writeInt64(mLongValue);
-            break;
-        case kBoolValueType:
-            err = out->writeBool(mBoolValue);
-            break;
-        case kFloatValueType:
-            err = out->writeFloat(mFloatValue);
-            break;
-        case kTupleValueType:
-            {
-                int sz = mTupleValue.size();
-                err = out->writeInt32(sz);
-                if (err != NO_ERROR) {
-                    return err;
-                }
-                for (int i = 0; i < sz; ++i) {
-                    err = mTupleValue[i].writeToParcel(out);
-                    if (err != NO_ERROR) {
-                        return err;
-                    }
-                }
-            }
-            break;
-        default:
-            err = UNKNOWN_ERROR;
-            break;
-    }
-    return err;
-}
-
-status_t
-StatsDimensionsValue::readFromParcel(const Parcel* in)
-{
-    // Implement me if desired. We don't currently use this.
-    ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented.");
-    (void)in; // To prevent compile error of unused parameter 'in'
-    return UNKNOWN_ERROR;
-}
-
-}  // namespace os
-}  // namespace android
diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/location/java/android/location/GnssRequest.aidl
similarity index 76%
copy from core/java/android/os/StatsDimensionsValue.aidl
copy to location/java/android/location/GnssRequest.aidl
index 81a14a4..581abcc 100644
--- a/core/java/android/os/StatsDimensionsValue.aidl
+++ b/location/java/android/location/GnssRequest.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
+/*
+ * Copyright (C) 2020, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.location;
 
-/** @hide */
-parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file
+/**
+ * @hide
+ */
+parcelable GnssRequest;
diff --git a/location/java/android/location/GnssRequest.java b/location/java/android/location/GnssRequest.java
new file mode 100644
index 0000000..2afb265
--- /dev/null
+++ b/location/java/android/location/GnssRequest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.location;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains extra parameters to pass to a GNSS provider implementation.
+ * @hide
+ */
+@SystemApi
+public final class GnssRequest implements Parcelable {
+    private final boolean mFullTracking;
+
+    /**
+     * Creates a {@link GnssRequest} with a full list of parameters.
+     */
+    private GnssRequest(boolean fullTracking) {
+        mFullTracking = fullTracking;
+    }
+
+    /**
+     * Represents whether to enable full GNSS tracking.
+     *
+     * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock
+     * discontinuities are expected, and when supported, carrier phase should be continuous in
+     * good signal conditions. All non-blacklisted, healthy constellations, satellites and
+     * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset
+     * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via
+     * duty cycling, constellations and frequency limits, etc.
+     */
+    public boolean isFullTracking() {
+        return mFullTracking;
+    }
+
+    @NonNull
+    public static final Creator<GnssRequest> CREATOR =
+            new Creator<GnssRequest>() {
+                @Override
+                @NonNull
+                public GnssRequest createFromParcel(@NonNull Parcel parcel) {
+                    return new GnssRequest(parcel.readBoolean());
+                }
+
+                @Override
+                public GnssRequest[] newArray(int i) {
+                    return new GnssRequest[i];
+                }
+            };
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append("GnssRequest[");
+        s.append("FullTracking=").append(mFullTracking);
+        s.append(']');
+        return s.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (!(obj instanceof GnssRequest)) return false;
+
+        GnssRequest other = (GnssRequest) obj;
+        if (mFullTracking != other.mFullTracking) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return mFullTracking ? 1 : 0;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeBoolean(mFullTracking);
+    }
+
+    /** Builder for {@link GnssRequest} */
+    public static final class Builder {
+        private boolean mFullTracking;
+
+        /**
+         * Constructs a {@link Builder} instance.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructs a {@link Builder} instance by copying a {@link GnssRequest}.
+         */
+        public Builder(@NonNull GnssRequest request) {
+            mFullTracking = request.isFullTracking();
+        }
+
+        /**
+         * Set the value of whether to enable full GNSS tracking, which is false by default.
+         *
+         * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock
+         * discontinuities are expected, and when supported, carrier phase should be continuous in
+         * good signal conditions. All non-blacklisted, healthy constellations, satellites and
+         * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset
+         * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via
+         * duty cycling, constellations and frequency limits, etc.
+         *
+         * <p>Full tracking requests always override non-full tracking requests. If any full
+         * tracking request occurs, all listeners on the device will receive full tracking GNSS
+         * measurements.
+         */
+        @NonNull public Builder setFullTracking(boolean value) {
+            mFullTracking = value;
+            return this;
+        }
+
+        /** Builds a {@link GnssRequest} instance as specified by this builder. */
+        @NonNull
+        public GnssRequest build() {
+            return new GnssRequest(mFullTracking);
+        }
+    }
+}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 197787e..7e6486c 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 {
@@ -2162,6 +2196,30 @@
     }
 
     /**
+     * Registers a GNSS Measurement callback.
+     *
+     * @param request  extra parameters to pass to GNSS measurement provider. For example, if {@link
+     *                 GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty
+     *                 cycling.
+     * @param executor the executor that the callback runs on.
+     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
+     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     * @throws IllegalArgumentException if request is null
+     * @throws IllegalArgumentException if executor is null
+     * @throws IllegalArgumentException if callback is null
+     * @throws SecurityException        if the ACCESS_FINE_LOCATION permission is not present
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, LOCATION_HARDWARE})
+    public boolean registerGnssMeasurementsCallback(
+            @NonNull GnssRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GnssMeasurementsEvent.Callback callback) {
+        throw new RuntimeException();
+    }
+
+    /**
      * Injects GNSS measurement corrections into the GNSS chipset.
      *
      * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
@@ -2397,19 +2455,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/AudioDeviceAddress.aidl b/media/java/android/media/AudioDevice.aidl
similarity index 94%
rename from media/java/android/media/AudioDeviceAddress.aidl
rename to media/java/android/media/AudioDevice.aidl
index 6a1a7f7..02071e5 100644
--- a/media/java/android/media/AudioDeviceAddress.aidl
+++ b/media/java/android/media/AudioDevice.aidl
@@ -15,4 +15,4 @@
 
 package android.media;
 
-parcelable AudioDeviceAddress;
+parcelable AudioDevice;
diff --git a/media/java/android/media/AudioDeviceAddress.java b/media/java/android/media/AudioDevice.java
similarity index 84%
rename from media/java/android/media/AudioDeviceAddress.java
rename to media/java/android/media/AudioDevice.java
index 3d8fc37..31ecc7b 100644
--- a/media/java/android/media/AudioDeviceAddress.java
+++ b/media/java/android/media/AudioDevice.java
@@ -28,7 +28,7 @@
 
 /**
  * @hide
- * Class to represent device type (speaker, headset...), address and role (input, output)
+ * Class to represent device type (speaker, headset...), address (if known) and role (input, output)
  * of an audio device.
  * <p>Unlike {@link AudioDeviceInfo}, the device
  * doesn't need to be connected to be uniquely identified, it can
@@ -39,7 +39,7 @@
  * permission, APIs using one rely on MODIFY_AUDIO_ROUTING.
  */
 @SystemApi
-public final class AudioDeviceAddress implements Parcelable {
+public final class AudioDevice implements Parcelable {
 
     /**
      * A role identifying input devices, such as microphones.
@@ -78,7 +78,7 @@
      *                   type and address.
      */
     @SystemApi
-    public AudioDeviceAddress(@NonNull AudioDeviceInfo deviceInfo) {
+    public AudioDevice(@NonNull AudioDeviceInfo deviceInfo) {
         Objects.requireNonNull(deviceInfo);
         mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
         mType = deviceInfo.getType();
@@ -93,7 +93,7 @@
      * @param address the address of the device, or an empty string for devices without one
      */
     @SystemApi
-    public AudioDeviceAddress(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
+    public AudioDevice(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
                               @NonNull String address) {
         Objects.requireNonNull(address);
         if (role != ROLE_OUTPUT && role != ROLE_INPUT) {
@@ -111,7 +111,7 @@
         mAddress = address;
     }
 
-    /*package*/ AudioDeviceAddress(int nativeType, @NonNull String address) {
+    /*package*/ AudioDevice(int nativeType, @NonNull String address) {
         mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT;
         mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
         mAddress = address;
@@ -157,7 +157,7 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
-        AudioDeviceAddress that = (AudioDeviceAddress) o;
+        AudioDevice that = (AudioDevice) o;
         return ((mRole == that.mRole)
                 && (mType == that.mType)
                 && mAddress.equals(that.mAddress));
@@ -170,7 +170,7 @@
 
     @Override
     public String toString() {
-        return new String("AudioDeviceAddress:"
+        return new String("AudioDevice:"
                 + " role:" + roleToString(mRole)
                 + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(
                         AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType))
@@ -191,25 +191,25 @@
         dest.writeString(mAddress);
     }
 
-    private AudioDeviceAddress(@NonNull Parcel in) {
+    private AudioDevice(@NonNull Parcel in) {
         mRole = in.readInt();
         mType = in.readInt();
         mAddress = in.readString();
     }
 
-    public static final @NonNull Parcelable.Creator<AudioDeviceAddress> CREATOR =
-            new Parcelable.Creator<AudioDeviceAddress>() {
+    public static final @NonNull Parcelable.Creator<AudioDevice> CREATOR =
+            new Parcelable.Creator<AudioDevice>() {
         /**
-         * Rebuilds an AudioDeviceAddress previously stored with writeToParcel().
-         * @param p Parcel object to read the AudioDeviceAddress from
-         * @return a new AudioDeviceAddress created from the data in the parcel
+         * Rebuilds an AudioDevice previously stored with writeToParcel().
+         * @param p Parcel object to read the AudioDevice from
+         * @return a new AudioDevice created from the data in the parcel
          */
-        public AudioDeviceAddress createFromParcel(Parcel p) {
-            return new AudioDeviceAddress(p);
+        public AudioDevice createFromParcel(Parcel p) {
+            return new AudioDevice(p);
         }
 
-        public AudioDeviceAddress[] newArray(int size) {
-            return new AudioDeviceAddress[size];
+        public AudioDevice[] newArray(int size) {
+            return new AudioDevice[size];
         }
     };
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7b17f9f..4a1088b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1596,7 +1596,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy,
-            @NonNull AudioDeviceAddress device) {
+            @NonNull AudioDevice device) {
         Objects.requireNonNull(strategy);
         Objects.requireNonNull(device);
         try {
@@ -1611,7 +1611,7 @@
     /**
      * @hide
      * Removes the preferred audio device previously set with
-     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}.
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDevice)}.
      * @param strategy the audio strategy whose routing will be affected
      * @return true if the operation was successful, false otherwise (invalid strategy, or no
      *     device set for example)
@@ -1632,14 +1632,14 @@
     /**
      * @hide
      * Return the preferred device for an audio strategy, previously set with
-     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDevice)}
      * @param strategy the strategy to query
      * @return the preferred device for that strategy, or null if none was ever set or if the
      *    strategy is invalid
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @Nullable AudioDeviceAddress getPreferredDeviceForStrategy(
+    public @Nullable AudioDevice getPreferredDeviceForStrategy(
             @NonNull AudioProductStrategy strategy) {
         Objects.requireNonNull(strategy);
         try {
@@ -4379,7 +4379,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+    public @NonNull List<AudioDevice> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         Objects.requireNonNull(attributes);
         final IAudioService service = getService();
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/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 02cb8aa..0a0f7f6 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1085,18 +1085,18 @@
      * @return an empty list if there was an issue with the request, a list of audio devices
      *   otherwise (typically one device, except for duplicated paths).
      */
-    public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+    public static @NonNull ArrayList<AudioDevice> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         Objects.requireNonNull(attributes);
-        final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+        final AudioDevice[] devices = new AudioDevice[MAX_DEVICE_ROUTING];
         final int res = getDevicesForAttributes(attributes, devices);
-        final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+        final ArrayList<AudioDevice> routeDevices = new ArrayList<>();
         if (res != SUCCESS) {
             Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
             return routeDevices;
         }
 
-        for (AudioDeviceAddress device : devices) {
+        for (AudioDevice device : devices) {
             if (device != null) {
                 routeDevices.add(device);
             }
@@ -1106,12 +1106,12 @@
 
     /**
      * Maximum number of audio devices a track is ever routed to, determines the size of the
-     * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+     * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDevice[])}
      */
     private static final int MAX_DEVICE_ROUTING = 4;
 
     private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
-                                                      @NonNull AudioDeviceAddress[] devices);
+                                                      @NonNull AudioDevice[] devices);
 
     /** @hide returns true if master mono is enabled. */
     public static native boolean getMasterMono();
@@ -1246,7 +1246,7 @@
      * @return {@link #SUCCESS} if successfully set
      */
     public static int setPreferredDeviceForStrategy(
-            int strategy, @NonNull AudioDeviceAddress device) {
+            int strategy, @NonNull AudioDevice device) {
         return setPreferredDeviceForStrategy(strategy,
                 AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
                 device.getAddress());
@@ -1277,7 +1277,7 @@
      *     and written to the array
      */
     public static native int getPreferredDeviceForStrategy(int strategy,
-                                                           AudioDeviceAddress[] device);
+                                                           AudioDevice[] device);
 
     // Items shared with audio service
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 767b67b..d237975 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1457,6 +1457,9 @@
     private int mRw2JpgFromRawOffset;
     private boolean mIsSupportedFile;
     private boolean mModified;
+    // XMP data can be contained as either part of the EXIF data (tag number 700), or as a
+    // separate data marker (a separate MARKER_APP1).
+    private boolean mXmpIsFromSeparateMarker;
 
     // Pattern to check non zero timestamp
     private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
@@ -2837,10 +2840,12 @@
                         final long offset = start + IDENTIFIER_XMP_APP1.length;
                         final byte[] value = Arrays.copyOfRange(bytes,
                                 IDENTIFIER_XMP_APP1.length, bytes.length);
-
+                        // TODO: check if ignoring separate XMP data when tag 700 already exists is
+                        //  valid.
                         if (getAttribute(TAG_XMP) == null) {
                             mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
                                     IFD_FORMAT_BYTE, value.length, offset, value));
+                            mXmpIsFromSeparateMarker = true;
                         }
                     }
                     break;
@@ -3445,11 +3450,24 @@
         }
         dataOutputStream.writeByte(MARKER_SOI);
 
+        // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not
+        // IDENTIFIER_EXIF_APP1)
+        // Will re-add it later after the rest of the file is written
+        ExifAttribute xmpAttribute = null;
+        if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) {
+            xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP);
+        }
+
         // Write EXIF APP1 segment
         dataOutputStream.writeByte(MARKER);
         dataOutputStream.writeByte(MARKER_APP1);
         writeExifSegment(dataOutputStream);
 
+        // Re-add previously removed XMP data.
+        if (xmpAttribute != null) {
+            mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute);
+        }
+
         byte[] bytes = new byte[4096];
 
         while (true) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 64c5c05..0fbc0d2 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -18,7 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.media.AudioAttributes;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioFocusInfo;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
@@ -274,13 +274,13 @@
 
     boolean isCallScreeningModeSupported();
 
-    int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAddress device);
+    int setPreferredDeviceForStrategy(in int strategy, in AudioDevice device);
 
     int removePreferredDeviceForStrategy(in int strategy);
 
-    AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
+    AudioDevice getPreferredDeviceForStrategy(in int strategy);
 
-    List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+    List<AudioDevice> getDevicesForAttributes(in AudioAttributes attributes);
 
     int setAllowedCapturePolicy(in int capturePolicy);
 
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 194669c..9131f3b 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -37,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 d0b5d48..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
@@ -520,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 515cabe..6418610 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -462,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
@@ -496,7 +471,8 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
+                if (route.isSystemRoute()
+                        || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     addedRoutes.add(route);
                 }
             }
@@ -512,7 +488,8 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.remove(route.getId());
-                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
+                if (route.isSystemRoute()
+                        || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     removedRoutes.add(route);
                 }
             }
@@ -528,7 +505,8 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
+                if (route.isSystemRoute()
+                        || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     changedRoutes.add(route);
                 }
             }
@@ -668,8 +646,8 @@
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
             RouteDiscoveryPreference discoveryRequest) {
         return routes.stream()
-                .filter(
-                        route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
+                .filter(route -> route.isSystemRoute()
+                        || route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
                 .collect(Collectors.toList());
     }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 662eeb1..2c1fdab 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -247,13 +247,23 @@
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
+        boolean transferred = false;
+        //TODO: instead of release all controllers, add an API to specify controllers that
+        // should be released (or is the system controller).
         for (RoutingController controller : getRoutingControllers(packageName)) {
-            if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) {
+            if (!transferred && controller.getSessionInfo().getTransferrableRoutes()
+                    .contains(route.getId())) {
                 controller.transferToRoute(route);
-                return;
+                transferred = true;
+            } else if (!controller.getSessionInfo().isSystemSession()) {
+                controller.release();
             }
         }
 
+        if (transferred) {
+            return;
+        }
+
         Client client;
         synchronized (sLock) {
             client = mClient;
@@ -294,30 +304,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/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index c25a533..6157ef4 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -25,7 +25,7 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioDeviceInfo;
 import android.media.AudioSystem;
 import android.os.Build;
@@ -476,12 +476,12 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
-    public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAddress device) {
+    public AudioEffect(@NonNull UUID uuid, @NonNull AudioDevice device) {
         this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid), 0, -2, Objects.requireNonNull(device));
     }
 
     private AudioEffect(UUID type, UUID uuid, int priority,
-            int audioSession, @Nullable AudioDeviceAddress device)
+            int audioSession, @Nullable AudioDevice device)
             throws IllegalArgumentException, UnsupportedOperationException,
             RuntimeException {
         int[] id = new int[1];
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 870c1b4..486c0c2 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -266,8 +266,12 @@
      * playback after the session has been stopped. If your app is started in
      * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
      * the pending intent.
+     * <p>
+     * The pending intent is recommended to be explicit to follow the security recommendation of
+     * {@link PendingIntent#getActivity}.
      *
      * @param mbr The {@link PendingIntent} to send the media button event to.
+     * @see PendingIntent#getActivity
      */
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
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/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index c1b17d3..63de0334 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -41,8 +41,7 @@
             FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
             FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
             FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
-            FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
-            FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
+            FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_MER,
             FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
             FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
     @Retention(RetentionPolicy.SOURCE)
@@ -125,18 +124,6 @@
     public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
             Constants.FrontendStatusType.LAYER_ERROR;
     /**
-     * CN value by VBER.
-     */
-    public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
-    /**
-     * CN value by LBER.
-     */
-    public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
-    /**
-     * CN value by XER.
-     */
-    public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
-    /**
      * Modulation Error Ratio.
      */
     public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
@@ -223,9 +210,6 @@
     private Integer mAgc;
     private Boolean mIsLnaOn;
     private boolean[] mIsLayerErrors;
-    private Integer mVberCn;
-    private Integer mLberCn;
-    private Integer mXerCn;
     private Integer mMer;
     private Integer mFreqOffset;
     private Integer mHierarchy;
@@ -403,33 +387,6 @@
         return mIsLayerErrors;
     }
     /**
-     * Gets CN value by VBER in thousandths of a deciBel (0.001dB).
-     */
-    public int getVberCn() {
-        if (mVberCn == null) {
-            throw new IllegalStateException();
-        }
-        return mVberCn;
-    }
-    /**
-     * Gets CN value by LBER in thousandths of a deciBel (0.001dB).
-     */
-    public int getLberCn() {
-        if (mLberCn == null) {
-            throw new IllegalStateException();
-        }
-        return mLberCn;
-    }
-    /**
-     * Gets CN value by XER in thousandths of a deciBel (0.001dB).
-     */
-    public int getXerCn() {
-        if (mXerCn == null) {
-            throw new IllegalStateException();
-        }
-        return mXerCn;
-    }
-    /**
      * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB).
      */
     public int getMer() {
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index f90144b..8105c74 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -67,7 +67,7 @@
     /** Frontend hierarchy. */
     void onHierarchy(@DvbtFrontendSettings.Hierarchy int hierarchy);
 
-    /** Frontend hierarchy. */
+    /** Frontend signal type. */
     void onSignalType(@AnalogFrontendSettings.SignalType int signalType);
 
 }
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 81a85c9..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) {
@@ -1162,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 978d9c6..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,6 +133,7 @@
     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);
@@ -158,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 f1a08f2..1a866ca 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -154,20 +154,6 @@
     }
 
     @Override
-    public void onUpdateVolume(String routeId, int 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/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index c1143ce..2e4d214 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -213,12 +213,12 @@
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
-    // Note: This recomputes the data space because it's possible the client has
-    // changed the output color space, so we cannot rely on it. Alternatively,
+    // Note: This recomputes the color type because it's possible the client has
+    // changed the output color type, so we cannot rely on it. Alternatively,
     // we could store the ADataSpace in the ImageDecoder.
     const ImageDecoder* imageDecoder = toDecoder(info);
     SkColorType colorType = imageDecoder->mCodec->computeOutputColorType(kN32_SkColorType);
-    sk_sp<SkColorSpace> colorSpace = imageDecoder->mCodec->computeOutputColorSpace(colorType);
+    sk_sp<SkColorSpace> colorSpace = imageDecoder->getDefaultColorSpace();
     return uirenderer::ColorSpaceToADataSpace(colorSpace.get(), colorType);
 }
 
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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 38cf5ab..617305c 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -38,7 +38,6 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsContract.Root;
-import android.provider.MediaStore;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -100,7 +99,6 @@
 
     private static final String ROOT_ID_PRIMARY_EMULATED =
             DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
-    private static final String ROOT_ID_HOME = "home";
 
     private static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
     private static final String GET_MEDIA_URI_CALL = "get_media_uri";
@@ -156,7 +154,6 @@
     private void updateVolumesLocked() {
         mRoots.clear();
 
-        VolumeInfo primaryVolume = null;
         final int userId = UserHandle.myUserId();
         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
         for (VolumeInfo volume : volumes) {
@@ -234,8 +231,6 @@
             }
 
             if (volume.isPrimary()) {
-                // save off the primary volume for subsequent "Home" dir initialization.
-                primaryVolume = volume;
                 root.flags |= Root.FLAG_ADVANCED;
             }
             // Dunno when this would NOT be the case, but never hurts to be correct.
@@ -259,37 +254,6 @@
             }
         }
 
-        // Finally, if primary storage is available we add the "Documents" directory.
-        // If I recall correctly the actual directory is created on demand
-        // by calling either getPathForUser, or getInternalPathForUser.
-        if (primaryVolume != null && primaryVolume.isVisible()) {
-            final RootInfo root = new RootInfo();
-            root.rootId = ROOT_ID_HOME;
-            mRoots.put(root.rootId, root);
-            root.title = getContext().getString(R.string.root_documents);
-
-            // Only report bytes on *volumes*...as a matter of policy.
-            root.reportAvailableBytes = false;
-            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH
-                    | Root.FLAG_SUPPORTS_IS_CHILD;
-
-            // Dunno when this would NOT be the case, but never hurts to be correct.
-            if (primaryVolume.isMountedWritable()) {
-                root.flags |= Root.FLAG_SUPPORTS_CREATE;
-            }
-
-            // Create the "Documents" directory on disk (don't use the localized title).
-            root.visiblePath = new File(
-                    primaryVolume.getPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
-            root.path = new File(
-                    primaryVolume.getInternalPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
-            try {
-                root.docId = getDocIdForFile(root.path);
-            } catch (FileNotFoundException e) {
-                throw new IllegalStateException(e);
-            }
-        }
-
         Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots");
 
         // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5
diff --git a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
index a06dc54..c4d8f35 100644
--- a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
+++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
@@ -29,7 +29,7 @@
         <service android:enabled="true"
                  android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService"
                  android:label="@string/app_name"
-                 android:exported="true">
+                 android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.LOAD_DATA" />
            </intent-filter>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 9c8345da..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/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml
new file mode 100644
index 0000000..04de174
--- /dev/null
+++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"
+      android:fillColor="#4285F4"/>
+</vector>
diff --git a/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml
new file mode 100644
index 0000000..b4145f2
--- /dev/null
+++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"
+      android:fillColor="#757575"/>
+</vector>
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
new file mode 100644
index 0000000..e6f8c01
--- /dev/null
+++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
@@ -0,0 +1,92 @@
+<?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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/entity_header"
+    style="@style/EntityHeader"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:id="@+id/entity_header_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:gravity="center_horizontal"
+        android:orientation="horizontal">
+
+        <LinearLayout
+            android:id="@+id/entity_header_content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:gravity="center_horizontal"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/entity_header_icon_personal"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:scaleType="fitCenter"
+                android:antialias="true"/>
+
+            <TextView
+                android:id="@+id/install_type"
+                style="@style/TextAppearance.EntityHeaderSummary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="Personal"/>
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/entity_header_swap_horiz"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:scaleType="fitCenter"
+            android:antialias="true"
+            android:src="@drawable/ic_swap_horiz_grey"/>
+
+        <LinearLayout
+            android:id="@+id/entity_header_content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:gravity="center_horizontal"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/entity_header_icon_work"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:scaleType="fitCenter"
+                android:antialias="true"/>
+            <TextView
+                android:id="@+id/install_type"
+                style="@style/TextAppearance.EntityHeaderSummary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="Work"/>
+        </LinearLayout>
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index b31841d..1a015a6 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet nie"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rooi-groen)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blou-geel)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurregstelling"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kleurregstelling help mense met kleurblindheid om akkurater kleure te sien"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tot battery gelaai is"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laai tans"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ingeprop; kan nie op die oomblik laai nie"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Vol"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 4955ad8..05f0e1b 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ምንም በይነመረብ የለም"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ፕሮታኖማሊ (ቀይ-አረንጓዴ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ትራይታኖማሊ (ሰማያዊ-ቢጫ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"የቀለም ማስተካከያ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ቀለም ማስተካከያ የቀለም ማየት የማይችሉ ሰዎች ተጨማሪ ትክክለኛ ቀለማትን እንዲመለከቱ ያስችላቸዋል"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ኃይል እስከሚሞላ ድረስ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ኃይል በመሙላት ላይ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ተሰክቷል፣ አሁን ኃይል መሙላት አይቻልም"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ሙሉነው"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 7b26be4..07befeb 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏متوفرة عبر %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"لا يتوفر اتصال إنترنت."</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"غطش الأحمر (الأحمر والأخضر)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"غمش الأزرق (الأزرق والأصفر)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحيح الألوان"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تساعد ميزة تصحيح الألوان المصابين بعمى الألوان على رؤية الألوان بدقة أكبر"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"جارٍ الشحن"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"تم التوصيل، ولكن يتعذّر الشحن الآن"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ممتلئة"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index dcebe5b..4d0d8cc 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইণ্টাৰনেট সংযোগ নাই"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্ৰ’টানোমালি (ৰঙা-সেউজীয়া)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্ৰাইটান\'মেলী (নীলা-হালধীয়া)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ৰং শুধৰণী"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ৰং শুধৰণী কৰা কার্যই বর্ণান্ধলোকসকলক ৰংবোৰ অধিক সঠিককৈ দেখাত সহায় কৰে"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> চাৰ্জ হ\'বলৈ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চ্চাৰ্জ হৈ আছে"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"চ্চাৰ্জ কৰা নাই"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ কৰি থোৱা হৈছে, এই মুহূৰ্তত চ্চাৰ্জ কৰিব নোৱাৰি"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"পূৰ্ণ"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 7f3db37..73eb48a 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yoxdur"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qırmızı-yaşıl)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (göy-sarı)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rəng düzəlişi"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Rəng düzəlişi rəng korluğu olanların daha yaxşı görməsinə kömək edir"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - Enerjinin dolmasına <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"enerji yığır"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Cihaz hazırda batareya yığa bilmir"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Tam"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index b789cb0..dff657e 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boja"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boja pomaže ljudima koji su daltonisti da preciznije vide boje"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"puni se"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno je, ali punjenje trenutno nije moguće"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index ce191eb..12c21a6 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Не падключана да інтэрнэту"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Пратанамалія (чырвоны-зялёны)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Трытанамалія (сіні-жоўты)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Карэкцыя колеру"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Карэкцыя колеру дазваляе людзям з парушэннямі колеравага зроку лепш распазнаваць выявы на экране"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> да поўнай зарадкі"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ідзе зарадка"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Падключана да сеткі сілкавання, зарадзіць зараз немагчыма"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятар зараджаны"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 3e6f77d..6fda5b3 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Няма интернет"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (червено – зелено)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синьо – жълто)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекция на цветове"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коригирането на цветовете помага на хората с цветна слепота да виждат по-точни цветове"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарежда се"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Включена в захранването, в момента не се зарежда"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Пълна"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 7f6938a..be2d1e0 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইন্টারনেট কানেকশন নেই"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্রোটানোম্যালি (লাল-সবুজ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্রিট্যানোম্যালি (নীল-হলুদ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"রঙ সংশোধন"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"রঙ অ্যাডজাস্ট করার সেটিংস, বর্ণান্ধতা আছে এমন ব্যক্তিদের আরও সঠিকভাবে রঙ চিনতে সাহায্য করে"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ সম্পূর্ণ চার্জ হয়ে যাবে"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চার্জ হচ্ছে"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ-ইন করা হয়েছে কিন্তু এখনই চার্জ করা যাবে না"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"পূর্ণ"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 0563abd..c32df18 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema internetske veze"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ispravka boje"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ispravka boje pomaže daltonistima da preciznije vide boje"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključen, trenutno se ne može puniti"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 71b2c5d..813fff4 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sense connexió a Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermell-verd)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (blau-groc)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correcció del color"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correcció del color ajuda les persones daltòniques a veure colors més precisos"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"s\'està carregant"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"El dispositiu està endollat però en aquests moments no es pot carregar"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index a381772..f116733 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nejste připojeni k internetu"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekce barev pomáhá barvoslepým lidem vidět přesnější barvy"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do nabití"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíjení"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Zapojeno, ale nelze nabíjet"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index f6e8576..e3b96a4 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Intet internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopi (rød-grøn)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopi (blå-gul)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korriger farver"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Farvekorrigering gør det nemmere for farveblinde at se farver mere nøjagtigt"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"oplader"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Enheden er tilsluttet en strømkilde. Det er ikke muligt at oplade på nuværende tidspunkt."</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Fuldt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 99a0910..1ddabcab 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Kein Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Die Farbkorrektur hilft farbenblinden Menschen, Farben besser zu erkennen"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> bis zur Aufladung"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"wird aufgeladen..."</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Angeschlossen, kann derzeit nicht geladen werden"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Voll"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index d37ea44..3a97d5a 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Χωρίς σύνδεση στο διαδίκτυο"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Πρωτανοπία (κόκκινο-πράσινο)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Τριτανοπία (μπλε-κίτρινο)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Διόρθωση χρωμάτων"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Η διόρθωση χρωμάτων βοηθάει τους ανθρώπους με αχρωματοψία να βλέπουν τα χρώματα με μεγαλύτερη ακρίβεια"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"φόρτιση"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Συνδέθηκε, δεν είναι δυνατή η φόρτιση αυτήν τη στιγμή"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Πλήρης"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 429cd3e..ec0a129 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 429cd3e..ec0a129 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 429cd3e..ec0a129 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 429cd3e..ec0a129 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 1aa6cdb..3b286f7 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎Connected via ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎Available via %1$s‎‏‎‎‏‎"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎Tap to sign up‎‏‎‎‏‎"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎No internet‎‏‎‎‏‎"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎Private DNS server cannot be accessed‎‏‎‎‏‎"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎Limited connection‎‏‎‎‏‎"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‎‎No internet‎‏‎‎‏‎"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎Protanomaly (red-green)‎‏‎‎‏‎"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎Tritanomaly (blue-yellow)‎‏‎‎‏‎"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎Color correction‎‏‎‎‏‎"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎Color correction helps people with color blindness to see more accurate colors‎‏‎‎‏‎"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎Overridden by ‎‏‎‎‏‏‎<xliff:g id="TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME_STRING">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left‎‏‎‎‏‎"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ until charged‎‏‎‎‏‎"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎Unknown‎‏‎‎‏‎"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎Charging‎‏‎‎‏‎"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎charging‎‏‎‎‏‎"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‎Not charging‎‏‎‎‏‎"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎Plugged in, can\'t charge right now‎‏‎‎‏‎"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎Full‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 7d28a32..966f2f2 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección de colores ayuda a las personas con daltonismo a ver colores más exactos"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar la carga"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. No se puede cargar en este momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Cargado"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index b35696f5..1198722 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección del color ayuda a las personas con daltonismo a ver los colores más reales"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> hasta que termine de cargarse)"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Se ha conectado, pero no se puede cargar en este momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index b6c112b..9be72f9 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Interneti pole"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värviparanduse funktsioon aitab värvipimedatel värve täpsemini näha"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laadimine"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Täis"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 4fd9add..d0c6c52 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ez dago Interneteko konexiorik"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kolore-zuzenketak kolore zehatzagoak ikusten laguntzen die koloreentzako itsutasuna duten pertsonei."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"kargatzen"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Konektatuta dago. Ezin da kargatu une honetan."</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Beteta"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 32a98be..f15174a 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏در دسترس از طریق %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبت‌نام ضربه بزنید"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"عدم اتصال به اینترنت"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"‏سرور DNS خصوصی قابل دسترسی نیست"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"قرمزدشواربینی (قرمز-سبز)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"آبی‌دشواربینی (آبی-زرد)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحیح رنگ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تصحیح رنگ به افراد مبتلا به کوررنگی کمک می‌کند رنگ‌ها را دقیق‌تر ببینند"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"درحال شارژ شدن"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمی‌شود"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"به برق وصل شده‌ است، درحال‌حاضر شارژ نمی‌شود"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"پر"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 84c5372..1232db3 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ei internetyhteyttä"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (puna-vihersokeus)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (sini-keltasokeus)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värikorjaus"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värinkorjaus auttaa värisokeita tulkitsemaan värejä"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täyteen lataukseen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ladataan"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kytketty virtalähteeseen, lataaminen ei onnistu"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Täynnä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 995eab6..6404df4 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucune connexion Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu/jaune)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en cours de charge"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"L\'appareil est branché, mais il ne peut pas être chargé pour le moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 2afacab..e75063b 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucun accès à Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction couleur"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en charge…"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Appareil branché, mais impossible de le charger pour le moment"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index a106e60..7fd32f9 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Non hai conexión a Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (vermello-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarelo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección da cor"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A corrección das cores axuda ás persoas con daltonismo a ver as cores con maior precisión"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectouse, pero non se pode cargar neste momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 5f2d5cd..1513c57 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"કોઈ ઇન્ટરનેટ નથી"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"પ્રોટેનોમલી (લાલ-લીલો)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ટ્રાઇટેનોમલી(વાદળી-પીળો)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"રંગ સુધારણા"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"રંગ સુધારણા રંગ અંધત્વવાળા લોકોને વધુ સચોટ રંગો જોવામાં સહાય કરે છે"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જ થવા માટે <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ચાર્જ થઈ રહ્યું છે"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"પ્લગ ઇન કરેલ, હમણાં ચાર્જ કરી શકતા નથી"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"પૂર્ણ"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index d60eead..c79c4a4 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट कनेक्शन नहीं है"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"लाल रंग पहचान न पाना (लाल-हरा)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"नीला रंग पहचान न पाना (नीला-पीला)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधार"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग में सुधार करने की सेटिंग, वर्णान्धता (कलर ब्लाइंडनेस) वाले लोगों को ज़्यादा सटीक रंग देखने में मदद करती है"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हो रही है"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन है, अभी चार्ज नहीं हो सकती"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"पूरी"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 28e3460..3ab5678 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno – zeleno)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo – žuto)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boje"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boje pomaže slijepima za boje da vide preciznije boje"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Uključen, trenutačno se ne može puniti"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 22e03a7..8d06f58 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nincs internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (piros– zöld)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (kék–sárga)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Színkorrekció"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A színkorrekció segít a színtévesztőknek abban, hogy pontosabban lássák a színeket"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a feltöltésig"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"töltés"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Csatlakoztatva, jelenleg nem tölt"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Feltöltve"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index ecb615a..99cef28 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Կապ չկա"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Պրոտանոմալիա (կարմիր-կանաչ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Տրիտանոմալիա (կապույտ-դեղին)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Գունաշտկում"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Գունաշտկումն օգնում է գունային դալտոնիզմ ունեցող մարդկանց ավելի ճշգրիտ տեսնել գույները"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"լիցքավորում"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքի աղբյուրին, սակայն այս պահին չի կարող լիցքավորվել"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Լիցքավորված է"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index cfbda04..25de382 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tidak ada internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koreksi warna"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Koreksi warna membantu orang penderita buta warna untuk melihat warna yang lebih akurat"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi terisi penuh"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengisi daya baterai"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Tercolok, tidak dapat mengisi baterai sekarang"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ba4c009..8fe382a 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Engin nettenging"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Litblinda (rauðgræn)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Litblinda (blágul)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Litaleiðrétting"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Litaleiðrétting hjálpar fólki með litblindu að sjá réttari liti"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> að fullri hleðslu"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"í hleðslu"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Í sambandi, ekki hægt að hlaða eins og er"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Fullhlaðin"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 50e5777..c9abc74 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internet assente"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalìa (rosso-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalìa (blu-giallo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correzione del colore"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correzione del colore consente alle persone daltoniche di vedere colori più accurati"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla carica completa"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"in carica"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Collegato alla corrente. Impossibile caricare al momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Carica"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 9f1e457..1921d03 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏זמינה דרך %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"אין אינטרנט"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"‏לא ניתן לגשת לשרת DNS הפרטי"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"פרוטנומליה (אדום-ירוק)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"טריטנומליה (כחול-צהוב)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"תיקון צבע"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"תיקון צבע עוזר למשתמשים עם עיוורון צבעים לראות צבעים מדויקים יותר"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד הטעינה"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"בטעינה"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"מלאה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 47020ed..e4e1ab9 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"インターネットに接続されていません"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string>
@@ -204,7 +203,7 @@
     <string name="vpn_settings_not_available" msgid="2894137119965668920">"このユーザーはVPN設定を利用できません。"</string>
     <string name="tethering_settings_not_available" msgid="266821736434699780">"このユーザーはテザリング設定を利用できません"</string>
     <string name="apn_settings_not_available" msgid="1147111671403342300">"このユーザーはアクセスポイント名設定を利用できません"</string>
-    <string name="enable_adb" msgid="8072776357237289039">"USBデバッグ"</string>
+    <string name="enable_adb" msgid="8072776357237289039">"USB デバッグ"</string>
     <string name="enable_adb_summary" msgid="3711526030096574316">"USB接続時はデバッグモードにする"</string>
     <string name="clear_adb_keys" msgid="3010148733140369917">"USBデバッグの許可の取り消し"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"バグレポートのショートカット"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"第一色弱(赤緑)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"第三色弱(青黄)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色補正"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色補正機能を利用すると、色覚異常のユーザーがより正確に色を判別できるようになります"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電完了まで <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電しています"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"接続されていますが、現在、充電できません"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"フル"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 116488d..b2cfb17 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ინტერნეტ-კავშირი არ არის"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"პროტოანომალია (წითელი-მწვანე)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ტრიტანომალია (ლურჯი-ყვითელი)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ფერის კორექცია"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ფერის კორექცია ეხმარება ფერითი სიბრმავის მქონე ადამიანებს, უფრო ზუსტად გაარჩიონ ფერები"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> დატენვამდე"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"იტენება"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"მიერთებულია, დატენვა ამჟამად ვერ ხერხდება"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ბატარეა დატენილია"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 3c4fdb7..756bc06 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ."</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (қызыл-жасыл)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсті түзету"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түсті түзету түсті ажырата алмайтын адамдарға оларды дәлірек көруге көмектеседі"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарядталуда"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Қосылған, зарядталмайды"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Толы"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 87a4f19..cdf92b3 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់​តាម <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុច​ដើម្បី​ចុះឈ្មោះ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"គ្មាន​អ៊ីនធឺណិតទេ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាច​ចូលប្រើ​ម៉ាស៊ីនមេ DNS ឯកជន​បានទេ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មាន​អ៊ីនធឺណិតទេ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ក្រហម​ពណ៌​បៃតង​)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ពណ៌​ខៀវ​-លឿង​)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ការ​កែ​ពណ៌"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ការកែតម្រូវ​ពណ៌​ជួយដល់​អ្នកដែល​មិនអាច​បែងចែក​ពណ៌​ឱ្យមើលឃើញ​ពណ៌ដែលត្រឹមត្រូវ​ជាមុន"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធ​ដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើប​ត្រូវសាក"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"មិន​ស្គាល់"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងបញ្ចូល​ថ្ម"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"កំពុង​សាក​ថ្ម"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុង​បញ្ចូល​ថ្ម"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ដោត​សាកថ្ម​រួចហើយ ប៉ុន្តែ​​សាកថ្ម​មិន​ចូលទេឥឡូវនេះ"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ពេញ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 144dddb..4a24691 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್‌ ಮಾಡಿ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ವರ್ಣ ಅಂಧತ್ವ ಹೊಂದಿರುವ ಜನರಿಗೆ ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ವೀಕ್ಷಿಸಲು ಬಣ್ಣ ತಿದ್ದುಪಡಿ ಸಹಾಯ ಮಾಡುತ್ತದೆ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್‌ ಆಗುತ್ತಿಲ್ಲ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆ, ಇದೀಗ ಚಾರ್ಜ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ಭರ್ತಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index df9f21d..51896e9 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"인터넷 연결 없음"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"적색약(적녹)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"청색약(청황)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"색보정"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"색상 보정을 사용하면 색맹인 사용자가 색상을 정확하게 구분하는 데 도움이 됩니다."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 완료까지 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"충전 중"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"전원이 연결되었지만 현재 충전할 수 없음"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"충전 완료"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 13c4144..061f6fd 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернет жок"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (кызыл-жашыл)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсүн тууралоо"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түстү тууралоо жөндөөсү түстөрдү айырмалап көрбөгөн адамдарга түстөрдү тагыраак билүүгө жардам берет"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин кубатталат"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"кубатталууда"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Сайылып турат, учурда кубаттоо мүмкүн эмес"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Толук"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 48224f4..bfbd8a9 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມ​ຕໍ່​ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"ມີ​ໃຫ້​ຜ່ານ %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ບໍ່ມີອິນເຕີເນັດ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ສີ​ແດງ​-ສີ​ຂຽວ​)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ສີ​ຟ້າ​-ສີ​ເຫຼືອງ​)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ການ​ປັບ​ແຕ່ງ​ສີ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ການແກ້ໄຂສີຈະຊ່ວຍໃຫ້ຄົນທີ່ຕາບອດສີເຫັນສີຕ່າງໆໄດ້ຖືກຕ້ອງຍິ່ງຂຶ້ນ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ກຳລັງສາກໄຟ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ສຽບສາຍແລ້ວ, ບໍ່ສາມາດສາກໄດ້ໃນຕອນນີ້"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ເຕັມ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index d080334..76d06fa 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nėra interneto ryšio"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (raudona, žalia)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (mėlyna, geltona)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Spalvų taisymas"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Naudojant spalvų taisymo funkciją daltonikai gali geriau matyti spalvas"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – iki visiškos įkrovos liko <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"įkraunama"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Įjungta į maitinimo lizdą, bet šiuo metu įkrauti neįmanoma"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Visiškai įkrautas"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index e13a50d..e4652d1 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nav interneta"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomālija (sarkans/zaļš)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomālija (zils/dzeltens)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Krāsu korekcija"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Krāsu korekcija palīdz cilvēkiem ar krāsu aklumu redzēt precīzākas krāsas."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"notiek uzlāde"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pievienots, taču pašlaik nevar veikt uzlādi"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Pilns"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index d027ffe..21b2f96 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернет"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (слепило за црвена и зелена)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (слепило за сина и жолта)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција на бои"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекцијата на бои им помага на луѓето со далтонизам попрецизно да ги гледаат боите"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до целосно полнење"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"се полни"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Приклучен е, но батеријата не може да се полни во моментов"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Полна"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e2a3d9f..85aa000 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ഇന്റർനെറ്റ് ഇല്ല"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്‌സസ് ചെയ്യാനാവില്ല"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്‌ഷൻ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"പ്രോട്ടാനോമലി (ചുവപ്പ്-പച്ച)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ട്രിട്ടാനോമലി (നീല-മഞ്ഞ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"വർണ്ണം ക്രമീകരിക്കൽ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"വർണ്ണം ശരിയാക്കൽ, വർണ്ണാന്ധത ബാധിച്ച ആളുകൾക്ക് നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ സഹായിക്കുന്നു"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമായി ചാർജാവാൻ <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ചാർജ് ചെയ്യുന്നു"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"പ്ലഗ് ഇൻ ചെയ്‌തു, ഇപ്പോൾ ചാർജ് ചെയ്യാനാവില്ല"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"നിറഞ്ഞു"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index dd08c9e..232148a 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернэт алга"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Өнгөний залруулга нь өнгөний сохортой хүмүүст илүү оновчтой өнгө харахад тусалдаг"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"цэнэглэж байна"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Залгаастай тул одоо цэнэглэх боломжгүй"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Дүүрэн"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index fb7cd1f..8767b4f 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्‍ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट नाही"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"क्षीण रक्तवर्णांधता (लाल-हिरवा)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"रंग दृष्टी कमतरता (निळा-पिवळा)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधारणा"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग सुधारणा ही वर्णांधता असलेल्या लोकांना रंग अधिक अचूक दिसण्यात मदत करते"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> पर्यंत पूर्ण चार्ज होईल"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज होत आहे"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन केलेले आहे, आता चार्ज करू शकत नाही"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 72b15ff..b79b801 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tiada Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pembetulan warna"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Pembetulan warna membantu orang yang mengalami kebutaan warna melihat warna yang lebih tepat"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> sehingga dicas"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengecas"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Dipalamkan, tidak boleh mengecas sekarang"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 24017ec..b2ad664 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"အင်တာနက် မရှိပါ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (အနီ-အစိမ်း)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (အပြာ-အဝါ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"အရောင်ပြင်ဆင်မှု"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"အရောင်ပြင်ဆင်ခြင်းက အရောင်ကန်းသူများအတွက် ပိုမိုမှန်ကန်သော အရောင်များဖြင့် ကြည့်နိုင်ရန် ကူညီမည်"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"အားသွင်းနေပါသည်"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ပလပ်ထိုးထားသောကြောင့် ယခုအားသွင်း၍ မရသေးပါ"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"အပြည့်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index aca13a7..6a1f158 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ingen internettilkobling"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rød-grønn)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blå-gul)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Fargekorrigering"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med fargekorrigering kan personer med fargeblindhet se mer nøyaktige farger"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"lader"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Laderen er koblet til – kan ikke lade akkurat nå"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index b2c7518..9cd5e20 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इन्टरनेट छैन"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रङ सुधार गर्नुले रङ छुट्याउन नसक्ने मान्छेलाई थप सही रङ देख्न मद्दत गर्दछ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"पूर्ण चार्ज हुन <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> लाग्छ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हुँदै"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लगइन गरिएको छ, अहिले नै चार्ज गर्न सकिँदैन"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण चार्ज भएको स्थिति"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index c74683b..8d5dea4 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rood-groen)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blauw-geel)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurcorrectie"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Met behulp van kleurcorrectie kunnen mensen die kleurenblind zijn, nauwkeurigere kleuren te zien krijgen"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tot opgeladen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"opladen"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Aangesloten, kan nu niet opladen"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Volledig"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 704fc42..c0489c1 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍‌ ନାହିଁ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ପ୍ରୋଟାନୋମାଲି (ଲାଲ୍‌-ସବୁଜ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ନୀଳ-ହଳଦିଆ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ରଙ୍ଗ ସଠିକତା"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"କଲର୍ କରେକ୍ସନ୍ ରଙ୍ଗ ଚିହ୍ନିବାରେ ସମସ୍ୟା ଥିବା ଲୋକମାନଙ୍କୁ ଅଧିକ ସଠିକ୍ ରଙ୍ଗ ଦେଖିବାରେ ସାହାଯ୍ୟ କରିଥାଏ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍‌ରାଇଡ୍‌ କରାଯାଇଛି"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ଚାର୍ଜ ହେଉଛି"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ପ୍ଲଗ୍‌ରେ ଲାଗିଛି, ହେଲେ ଏବେ ଚାର୍ଜ କରିପାରିବ ନାହିଁ"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ଚାର୍ଜ ସମ୍ପୂର୍ଣ୍ଣ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index bf5a32c..7355418 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ਰੰਗ ਸੁਧਾਈ ਨਾਲ ਰੰਗਾਂ ਦੇ ਅੰਨ੍ਹਾਪਣ ਦੇ ਸ਼ਿਕਾਰ ਲੋਕਾਂ ਦੀ ਵਧੇਰੇ ਸਟੀਕ ਰੰਗਾਂ ਨੂੰ ਦੇਖਣ ਵਿੱਚ ਮਦਦ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ਚਾਰਜ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ਪਲੱਗ ਲੱਗਾ ਹੋਇਆ ਹੈ, ਇਸ ਸਮੇਂ ਚਾਰਜ ਨਹੀਂ ਹੋ ਸਕਦੀ"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"ਪੂਰੀ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index d907760..a3ccb07 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Brak internetu"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (czerwony-zielony)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (niebieski-żółty)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcja kolorów"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcja kolorów pomaga osobom z zaburzeniami rozpoznawania barw lepiej je widzieć."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ładowanie"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Podłączony. Nie można teraz ładować"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Naładowana"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index e61c84e..f4182bf 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 8ceeb63..2f206d4 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção da cor"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cor ajuda as pessoas com daltonismo a ver cores mais precisas."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até ficar carregada"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"a carregar…"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ligada à corrente, não é possível carregar neste momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Completo"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index e61c84e..f4182bf 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 5ad8dfd..6e8bbd1 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Fără conexiune la internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Corecția culorii ajută persoanele cu daltonism să vadă culori mai exacte"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la încărcare"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"se încarcă"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectat, nu se poate încărca chiar acum"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Complet"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 104efdc..87831c7 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нет подключения к Интернету"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (красный/зеленый)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синий/желтый)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Коррекция цвета"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коррекция цвета помогает пользователям с нарушениями цветового зрения лучше различать изображение на экране."</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряжается"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Подключено, не заряжается"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Батарея заряжена"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index f5dad87..f0a823a 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"අන්තර්ජාලය නැත"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්‍රවේශ වීමට නොහැකිය"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"වර්ණ දුර්වලතාවය (රතු-කොළ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"වර්ණ අන්ධතාවය (නිල්-කහ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"වර්ණ නිවැරදි කිරීම"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"වර්ණ අන්ධතාවෙන් පෙළෙන පුද්ගලයන්ට වඩාත් නිරවද්‍ය වර්ණ බැලීමට වර්ණ නිවැරදි කිරීම සහාය වේ"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ආරෝපණය වේ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"පේනුගත කර ඇත, මේ අවස්ථාවේදී ආරෝපණය කළ නොහැකිය"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"පූර්ණ"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 527dafb..fc2ba1d 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Bez internetu"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (červená a zelená)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (modrá a žltá)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Úprava farieb"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcia farieb pomáha farboslepým ľuďom vidieť presnejšie farby"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíja sa"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pripojené, ale nie je možné nabíjať"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 4816950..cda2e06 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ni internetne povezave"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (rdeča – zelena)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (modra – rumena)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Popravljanje barv"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Popravljanje barv osebam z barvno slepoto pomaga, da vidijo bolj prave barve"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"polnjenje"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno, trenutno ni mogoče polniti"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Poln"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 12511dd3..9854017 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nuk ka internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korrigjimi i ngjyrës i ndihmon njerëzit me daltonizëm të shohin ngjyra më të sakta"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> deri sa të karikohen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"po karikohet"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Në prizë, por nuk mund të ngarkohet për momentin"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"E mbushur"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 4724be5..b59a568 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернета"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (црвено-зелено)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (плаво-жуто)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција боја"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекција боја помаже људима који су далтонисти да прецизније виде боје"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"пуни се"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Прикључено је, али пуњење тренутно није могуће"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Пуна"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 9d1bc42..f9ed6d4 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Inget internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rött-grönt)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blått-gult)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Färgkorrigering"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med färgkorrigering kan färgblinda personer se mer korrekta färger"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> till full laddning"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laddas"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Inkopplad, kan inte laddas just nu"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1e2489f..40e025a 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Hakuna intaneti"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (nyekundu-kijani)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (samawati-manjano)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Usahihishaji wa rangi"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Urekebishaji wa rangi huwasaidia watu wenye matatizo ya kutofautisha rangi ili waone rangi nyingi sahihi"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hadi ijae chaji"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"inachaji"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Haiwezi kuchaji kwa sasa"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Imejaa"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index fcb801b..36a92ed 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"இணைய இணைப்பு இல்லை"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"வண்ணத்திருத்தம்"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"வண்ணத் திருத்தத்தால் நிறக்குருடு உள்ளவர்களால் வண்ணங்களை இன்னும் துல்லியமாகப் பார்க்க முடியும்"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"சார்ஜ் ஆகிறது"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"செருகப்பட்டது, ஆனால் இப்போது சார்ஜ் செய்ய முடியவில்லை"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"முழுவதும் சார்ஜ் ஆனது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 7a80451..5ea380c 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ఇంటర్నెట్ లేదు"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్‌ను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"రంగు సవరణ అనేది వర్ణాంధత్వం ఉన్న వ్యక్తులకు మరింత ఖచ్చితమైన రంగులను చూడడానికి సహాయపడుతుంది"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ఛార్జ్ అవుతోంది"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ప్లగ్ ఇన్ చేయబడింది, ప్రస్తుతం ఛార్జ్ చేయడం సాధ్యం కాదు"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"నిండింది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 130e1c4..15e74ae 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ไม่มีอินเทอร์เน็ต"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ตาบอดจางสีแดง (สีแดง/เขียว)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ตาบอดจางสีน้ำเงิน (สีน้ำเงิน/เหลือง)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"การแก้สี"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"การแก้ไขสีช่วยให้ผู้ที่มีอาการตาบอดสีเห็นสีต่างๆ ได้ตรงตามจริงยิ่งขึ้น"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"กำลังชาร์จ"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"เสียบอยู่ ไม่สามารถชาร์จได้ในขณะนี้"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"เต็ม"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index bc1a405..8012244 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Walang internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (pula-berde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (asul-dilaw)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pagtatama ng kulay"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ang pagwawasto ng kulay ay nakakatulong sa mga taong may color blindness na makita ang mga mas tamang kulay"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hanggang matapos mag-charge"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nagcha-charge"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Nakasaksak, hindi makapag-charge sa ngayon"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Puno"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 4e263ef..b83ffcb 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yok"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Kırmızı renk körlüğü (kırmızı-yeşil)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mavi renk körlüğü (mavi-sarı)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Renk düzeltme"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Renk düzeltme, renk körlüğü olan kişilerin daha doğru renkler görmelerine yardımcı olur"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - şarj olmaya <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"şarj oluyor"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Prize takıldı, şu anda şarj olamıyor"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Dolu"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index ad9f600..5fb46f8 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Немає Інтернету"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалія (червоний – зелений)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалія (синій – жовтий)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекція кольору"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекція кольору допомагає людям із дальтонізмом бачити точніші кольори"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряджається"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Підключено. Не вдається зарядити"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятор заряджено"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index b995da1..c6c10f0 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏دستیاب بذریعہ ‎%1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"انٹرنیٹ نہیں ہے"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"‏نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"‏Protanomaly (سرخ سبز)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"‏Tritanomaly (نیلا پیلا)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"رنگ کی درستگی رنگ نہ دکھائی دینے والے لوگوں کی رنگوں کو مزید درست طریقے سے دیکھنے میں مدد کرتی ہے"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> چارج ہونے تک"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"چارج ہو رہا ہے"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"پلگ ان ہے، ابھی چارج نہیں کر سکتے"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"مکمل"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index f02b639..07a6249 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internetga ulanmagansiz"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qizil/yashil)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (ko‘k/sariq)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rangni tuzatish"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ranglarni sozlash ranglarni farqlashda muammosi bor insonlarga (masalan, daltoniklarga) aniq koʻrishda yordam beradi"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻliq quvvat oladi"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"quvvat olmoqda"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ulangan, lekin quvvat olmayapti"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"To‘la"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 8fcf1f2..7e3944c 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Không có Internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Mù màu đỏ không hoàn toàn (đỏ-xanh lục)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mù màu (xanh lam-vàng)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Sửa màu"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Tùy chọn sửa màu giúp những người bị mù màu thấy màu sắc chính xác hơn"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là sạc xong"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"đang sạc"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Đã cắm nhưng không thể sạc ngay"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Đầy"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 755cbee..3edbb4d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"无法访问互联网"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"红色弱视(红绿不分)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"蓝色弱视(蓝黄不分)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"颜色校正功能有助于色盲用户看到更准确的颜色"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>后充满电"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充电"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入电源,但是现在无法充电"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"电量充足"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e50acf5..6174fe9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有互聯網連線"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅綠)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍黃)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正有助色盲人士看到更準確的顏色"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充電"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入電源插座,但目前無法充電"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"電量已滿"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 7df6fed..a150d16 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有網際網路連線"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅-綠)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍-黃)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正可協助色盲使用者看見較準確的色彩"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電中"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已接上電源,但現在無法充電"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"電力充足"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 46dffbe..003dda3 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -42,8 +42,7 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string>
-    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
-    <skip />
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ayikho i-inthanethi"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string>
@@ -384,7 +383,8 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"I-Protanomaly (bomvu-luhlaza)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"I-Tritanomaly (luhlaza okwesibhakabhaka-phuzi)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ukulungiswa kombala"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ukulungisa umbala kusiza abantu abangaboni imibala ukubona ngokuqondile"</string>
+    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) -->
+    <skip />
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string>
@@ -414,7 +414,10 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ize igcwale"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
-    <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"iyashaja"</string>
+    <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) -->
+    <skip />
+    <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) -->
+    <skip />
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kuxhunyiwe, ayikwazi ukushaja khona manje"</string>
     <string name="battery_info_status_full" msgid="4443168946046847468">"Kugcwele"</string>
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..804e0cb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -553,6 +553,60 @@
     <string name="enable_adb_summary">Debug mode when USB is connected</string>
     <!-- Setting title to revoke secure USB debugging authorizations -->
     <string name="clear_adb_keys">Revoke USB debugging authorizations</string>
+    <!-- [CHAR LIMIT=32] Setting title for ADB wireless switch -->
+    <string name="enable_adb_wireless">Wireless debugging</string>
+    <!-- [CHAR LIMIT=NONE] Setting checkbox summary for whether to enable Wireless debugging support on the phone -->
+    <string name="enable_adb_wireless_summary">Debug mode when Wi\u2011Fi is connected</string>
+    <!-- [CHAR LIMIT=32] Summary text when ADB wireless has error -->
+    <string name="adb_wireless_error">Error</string>
+    <!-- [CHAR LIMIT=32] Setting title for ADB wireless fragment -->
+    <string name="adb_wireless_settings">Wireless debugging</string>
+    <!-- [CHAR LIMIT=NONE] Wireless debugging settings. text displayed when wireless debugging is off and network list is empty. -->
+    <string name="adb_wireless_list_empty_off">To see and use available devices, turn on wireless debugging</string>
+    <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference -->
+    <string name="adb_pair_method_qrcode_title">Pair device with QR code</string>
+    <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference -->
+    <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string>
+    <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference -->
+    <string name="adb_pair_method_code_title">Pair device with pairing code</string>
+    <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference -->
+    <string name="adb_pair_method_code_summary">Pair new devices using six digit code</string>
+    <!-- [CHAR LIMIT=50] Title for adb wireless paired devices category -->
+    <string name="adb_paired_devices_title">Paired devices</string>
+    <!-- [CHAR LIMIT=50] Summary for adb wireless paired device preference -->
+    <string name="adb_wireless_device_connected_summary">Currently connected</string>
+    <!-- [CHAR LIMIT=50] Title for the adb device details fragment -->
+    <string name="adb_wireless_device_details_title">Device details</string>
+    <!-- [CHAR LIMIT=16] Button label to forget an adb device -->
+    <string name="adb_device_forget">Forget</string>
+    <!-- [CHAR LIMIT=50] Title format for mac address preference in adb device details fragment -->
+    <string name="adb_device_fingerprint_title_format">Device fingerprint: <xliff:g id="fingerprint_param" example="a1:b2:c3:d4:e5:f6">%1$s</xliff:g></string>
+    <!-- [CHAR LIMIT=50] Title for adb wireless connection failed dialog -->
+    <string name="adb_wireless_connection_failed_title">Connection unsuccessful</string>
+    <!-- [CHAR LIMIT=NONE] Message for adb wireless connection failed dialog -->
+    <string name="adb_wireless_connection_failed_message">Make sure <xliff:g id="device_name" example="Bob's Macbook">%1$s</xliff:g> is connected to the correct network</string>
+    <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog title -->
+    <string name="adb_pairing_device_dialog_title">Pair with device</string>
+    <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog pairing code label -->
+    <string name="adb_pairing_device_dialog_pairing_code_label">Wi\u2011Fi pairing code</string>
+    <!-- [CHAR LIMIT=50] Adb Wireless pairing device failed dialog title -->
+    <string name="adb_pairing_device_dialog_failed_title">Pairing unsuccessful</string>
+    <!-- [CHAR LIMIT=NONE] Adb wireless pairing device failed dialog message -->
+    <string name="adb_pairing_device_dialog_failed_msg">Make sure the device is connected to the same network.</string>
+    <!-- [CHAR LIMIT=NONE] Adb wireless qr code scanner description -->
+    <string name="adb_wireless_qrcode_summary">Pair device over Wi\u2011Fi by scanning a QR code</string>
+    <!-- [CHAR LIMIT=NONE] Adb wireless QR code pairing in progress text -->
+    <string name="adb_wireless_verifying_qrcode_text">Pairing device\u2026</string>
+    <!-- [CHAR LIMIT=NONE] Adb wireless QR code failed message -->
+    <string name="adb_qrcode_pairing_device_failed_msg">Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network.</string>
+    <!-- [CHAR LIMIT=50] Adb Wireless ip address and port title -->
+    <string name="adb_wireless_ip_addr_preference_title">IP address \u0026 Port</string>
+    <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title -->
+    <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string>
+    <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description -->
+    <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string>
+    <!--Adb wireless search Keywords [CHAR LIMIT=NONE]-->
+    <string name="keywords_adb_wireless">adb, debug, dev</string>
     <!-- [CHAR LIMIT=NONE] Setting checkbox title for Whether to include bug report item in power menu. -->
     <string name="bugreport_in_power">Bug report shortcut</string>
     <!-- [CHAR LIMIT=NONE] Setting checkbox summary for Whether to include bug report item in power -->
@@ -689,6 +743,10 @@
     <string name="adb_warning_title">Allow USB debugging?</string>
     <!-- Warning text to user about the implications of enabling USB debugging -->
     <string name="adb_warning_message">USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string>
+    <!-- Title of warning dialog about the implications of enabling USB debugging [CHAR LIMIT=NONE] -->
+    <string name="adbwifi_warning_title">Allow wireless debugging?</string>
+    <!-- Warning text to user about the implications of enabling USB debugging [CHAR LIMIT=NONE] -->
+    <string name="adbwifi_warning_message">Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string>
     <!-- Message of dialog confirming that user wants to revoke access to adb from all computers they have authorized -->
     <string name="adb_keys_warning_message">Revoke access to USB debugging from all computers you\u2019ve previously authorized?</string>
     <!-- Title of warning dialog about the implications of enabling developer settings -->
@@ -970,7 +1028,7 @@
     <!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
     <string name="accessibility_display_daltonizer_preference_title">Color correction</string>
     <!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with color blindness to see more accurate colors</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with colorblindness see more accurate colors</string>
     <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
     <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
 
@@ -1034,8 +1092,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/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 3a53d29..e551b69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,7 +19,8 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
 import android.util.Pair;
 
 import com.android.settingslib.R;
@@ -35,8 +36,9 @@
 
     private CachedBluetoothDevice mCachedDevice;
 
-    BluetoothMediaDevice(Context context, CachedBluetoothDevice device) {
-        super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+    BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
+            MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
+        super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName);
         mCachedDevice = device;
         initDeviceRecord();
     }
@@ -65,20 +67,6 @@
         return MediaDeviceUtils.getId(mCachedDevice);
     }
 
-    @Override
-    public boolean connect() {
-        //TODO(b/117129183): add callback to notify LocalMediaManager connection state.
-        final boolean isConnected = mCachedDevice.setActive();
-        setConnectedRecord();
-        Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
-        return isConnected;
-    }
-
-    @Override
-    public void disconnect() {
-        //TODO(b/117129183): disconnected last select device
-    }
-
     /**
      * Get current CachedBluetoothDevice
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
index 12d054e..d84788b 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);
             }
@@ -157,7 +157,7 @@
     private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
         MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
         if (mediaDevice == null) {
-            mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
+            mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, null, null, null);
             cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
             mLastAddedDevice = mediaDevice;
             mMediaDevices.add(mediaDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index d287f95..b9081f2 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;
@@ -30,16 +33,9 @@
 
     private static final String TAG = "InfoMediaDevice";
 
-    private final MediaRoute2Info mRouteInfo;
-    private final MediaRouter2Manager mRouterManager;
-    private final String mPackageName;
-
     InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
-        super(context, MediaDeviceType.TYPE_CAST_DEVICE);
-        mRouterManager = routerManager;
-        mRouteInfo = info;
-        mPackageName = packageName;
+        super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName);
         initDeviceRecord();
     }
 
@@ -67,13 +63,6 @@
     }
 
     @Override
-    public boolean connect() {
-        setConnectedRecord();
-        mRouterManager.selectRoute(mPackageName, mRouteInfo);
-        return true;
-    }
-
-    @Override
     public void requestSetVolume(int volume) {
         mRouterManager.requestSetVolume(mRouteInfo, volume);
     }
@@ -89,11 +78,30 @@
     }
 
     @Override
-    public void disconnect() {
-        //TODO(b/144535188): disconnected last select device
+    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);
+    }
+
     public boolean isConnected() {
         return true;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index a4be46c..e910967 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -15,13 +15,23 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.DEVICE_TYPE_BLUETOOTH;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_UNKNOWN;
+
 import android.app.Notification;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -44,11 +54,14 @@
     String mPackageName;
 
     private MediaDevice mCurrentConnectedDevice;
+    private LocalBluetoothManager mBluetoothManager;
 
-    public InfoMediaManager(Context context, String packageName, Notification notification) {
+    public InfoMediaManager(Context context, String packageName, Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
         super(context, notification);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
+        mBluetoothManager = localBluetoothManager;
         if (!TextUtils.isEmpty(packageName)) {
             mPackageName = packageName;
         }
@@ -94,23 +107,73 @@
 
     private void buildAllRoutes() {
         for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
-            final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route,
-                    mPackageName);
-            mMediaDevices.add(device);
+            addMediaDevice(route);
         }
     }
 
     private void buildAvailableRoutes() {
         for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
-            final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route,
-                    mPackageName);
-            if (TextUtils.equals(route.getClientPackageName(), mPackageName)) {
-                mCurrentConnectedDevice = device;
-            }
-            mMediaDevices.add(device);
+            addMediaDevice(route);
         }
     }
 
+    private void addMediaDevice(MediaRoute2Info route) {
+        final int deviceType = route.getDeviceType();
+        MediaDevice mediaDevice = null;
+        switch (deviceType) {
+            case DEVICE_TYPE_UNKNOWN:
+                //TODO(b/148765806): use correct device type once api is ready.
+                final String defaultRoute = "DEFAULT_ROUTE";
+                if (TextUtils.equals(defaultRoute, route.getOriginalId())) {
+                    mediaDevice =
+                            new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+                } else {
+                    mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
+                            mPackageName);
+                    if (!TextUtils.isEmpty(mPackageName)
+                            && TextUtils.equals(route.getClientPackageName(), mPackageName)) {
+                        mCurrentConnectedDevice = mediaDevice;
+                    }
+                }
+                break;
+            case DEVICE_TYPE_REMOTE_TV:
+                break;
+            case DEVICE_TYPE_BLUETOOTH:
+                final BluetoothDevice device =
+                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId());
+                final CachedBluetoothDevice cachedDevice =
+                        mBluetoothManager.getCachedDeviceManager().findDevice(device);
+                mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+                        route, mPackageName);
+                break;
+            default:
+                Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
+                break;
+
+        }
+
+        if (mediaDevice != null) {
+            mMediaDevices.add(mediaDevice);
+        }
+    }
+
+    /**
+     * Transfer MediaDevice for media without package name.
+     */
+    public boolean connectDeviceWithoutPackageName(MediaDevice device) {
+        boolean isConnected = false;
+        final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
+        if (infos.size() > 0) {
+            final RoutingSessionInfo info = infos.get(0);
+            final MediaRouter2Manager.RoutingController controller =
+                    mRouterManager.getControllerForSession(info);
+
+            controller.transferToRoute(device.mRouteInfo);
+            isConnected = true;
+        }
+        return isConnected;
+    }
+
     class RouterManagerCallback extends MediaRouter2Manager.Callback {
 
         @Override
@@ -124,5 +187,10 @@
                 refreshDevices();
             }
         }
+
+        @Override
+        public void onRoutesChanged(List<MediaRoute2Info> routes) {
+            refreshDevices();
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 50196d2..a1342ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -18,6 +18,7 @@
 import android.app.Notification;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.IntDef;
@@ -57,7 +58,6 @@
     final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
 
     private Context mContext;
-    private BluetoothMediaManager mBluetoothMediaManager;
     private LocalBluetoothManager mLocalBluetoothManager;
     private InfoMediaManager mInfoMediaManager;
     private String mPackageName;
@@ -97,18 +97,18 @@
             return;
         }
 
-        mBluetoothMediaManager =
-                new BluetoothMediaManager(context, mLocalBluetoothManager, notification);
-        mInfoMediaManager = new InfoMediaManager(context, packageName, notification);
+        mInfoMediaManager =
+                new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager);
     }
 
     @VisibleForTesting
     LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
-            BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) {
+            InfoMediaManager infoMediaManager, String packageName) {
         mContext = context;
         mLocalBluetoothManager = localBluetoothManager;
-        mBluetoothMediaManager = bluetoothMediaManager;
         mInfoMediaManager = infoMediaManager;
+        mPackageName = packageName;
+
     }
 
     /**
@@ -135,7 +135,12 @@
             mCurrentConnectedDevice.disconnect();
         }
 
-        final boolean isConnected = device.connect();
+        boolean isConnected = false;
+        if (TextUtils.isEmpty(mPackageName)) {
+            isConnected = mInfoMediaManager.connectDeviceWithoutPackageName(device);
+        } else {
+            isConnected = device.connect();
+        }
         if (isConnected) {
             mCurrentConnectedDevice = device;
         }
@@ -159,29 +164,10 @@
      */
     public void startScan() {
         mMediaDevices.clear();
-        mBluetoothMediaManager.registerCallback(mMediaDeviceCallback);
-        mBluetoothMediaManager.startScan();
         mInfoMediaManager.registerCallback(mMediaDeviceCallback);
         mInfoMediaManager.startScan();
     }
 
-    private void addPhoneDeviceIfNecessary() {
-        // add phone device to list if there have any Bluetooth device and cast device.
-        if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) {
-            if (mPhoneDevice == null) {
-                mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
-            }
-            mMediaDevices.add(mPhoneDevice);
-        }
-    }
-
-    private void removePhoneMediaDeviceIfNecessary() {
-        // if PhoneMediaDevice is the last item in the list, remove it.
-        if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) {
-            mMediaDevices.clear();
-        }
-    }
-
     void dispatchDeviceListUpdate() {
         synchronized (mCallbacks) {
             Collections.sort(mMediaDevices, COMPARATOR);
@@ -203,8 +189,6 @@
      * Stop scan MediaDevice
      */
     public void stopScan() {
-        mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback);
-        mBluetoothMediaManager.stopScan();
         mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
         mInfoMediaManager.stopScan();
     }
@@ -227,6 +211,22 @@
     }
 
     /**
+     * Find the MediaDevice from all media devices by id.
+     *
+     * @param id the unique id of MediaDevice
+     * @return MediaDevice
+     */
+    public MediaDevice getMediaDeviceById(String id) {
+        for (MediaDevice mediaDevice : mMediaDevices) {
+            if (mediaDevice.getId().equals(id)) {
+                return mediaDevice;
+            }
+        }
+        Log.i(TAG, "Unable to find device " + id);
+        return null;
+    }
+
+    /**
      * Find the current connected MediaDevice.
      *
      * @return MediaDevice
@@ -235,15 +235,34 @@
         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() {
+        MediaDevice phoneMediaDevice = null;
         for (MediaDevice device : mMediaDevices) {
             if (device instanceof  BluetoothMediaDevice) {
                 if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) {
                     return device;
                 }
+            } else if (device instanceof PhoneMediaDevice) {
+                phoneMediaDevice = device;
             }
         }
-        return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null;
+        return mMediaDevices.contains(phoneMediaDevice) ? phoneMediaDevice : null;
     }
 
     private boolean isConnected(CachedBluetoothDevice device) {
@@ -256,38 +275,24 @@
         public void onDeviceAdded(MediaDevice device) {
             if (!mMediaDevices.contains(device)) {
                 mMediaDevices.add(device);
-                addPhoneDeviceIfNecessary();
                 dispatchDeviceListUpdate();
             }
         }
 
         @Override
         public void onDeviceListAdded(List<MediaDevice> devices) {
-            for (MediaDevice device : devices) {
-                if (getMediaDeviceById(mMediaDevices, device.getId()) == null) {
-                    mMediaDevices.add(device);
-                }
-            }
-            addPhoneDeviceIfNecessary();
+            mMediaDevices.clear();
+            mMediaDevices.addAll(devices);
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
             mCurrentConnectedDevice = infoMediaDevice != null
                     ? infoMediaDevice : updateCurrentConnectedDevice();
-            updatePhoneMediaDeviceSummary();
             dispatchDeviceListUpdate();
         }
 
-        private void updatePhoneMediaDeviceSummary() {
-            if (mPhoneDevice != null) {
-                ((PhoneMediaDevice) mPhoneDevice)
-                        .updateSummary(mCurrentConnectedDevice == mPhoneDevice);
-            }
-        }
-
         @Override
         public void onDeviceRemoved(MediaDevice device) {
             if (mMediaDevices.contains(device)) {
                 mMediaDevices.remove(device);
-                removePhoneMediaDeviceIfNecessary();
                 dispatchDeviceListUpdate();
             }
         }
@@ -295,7 +300,6 @@
         @Override
         public void onDeviceListRemoved(List<MediaDevice> devices) {
             mMediaDevices.removeAll(devices);
-            removePhoneMediaDeviceIfNecessary();
             dispatchDeviceListUpdate();
         }
 
@@ -308,7 +312,6 @@
                 return;
             }
             mCurrentConnectedDevice = connectDevice;
-            updatePhoneMediaDeviceSummary();
             dispatchDeviceAttributesChanged();
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 839d528..580e086 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -17,9 +17,12 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
 import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -40,14 +43,23 @@
         int TYPE_BLUETOOTH_DEVICE = 3;
     }
 
+    @VisibleForTesting
+    int mType;
+
     private int mConnectedRecord;
 
-    protected Context mContext;
-    protected int mType;
+    protected final Context mContext;
+    protected final MediaRoute2Info mRouteInfo;
+    protected final MediaRouter2Manager mRouterManager;
+    protected final String mPackageName;
 
-    MediaDevice(Context context, @MediaDeviceType int type) {
+    MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager,
+            MediaRoute2Info info, String packageName) {
         mType = type;
         mContext = context;
+        mRouteInfo = info;
+        mRouterManager = routerManager;
+        mPackageName = packageName;
     }
 
     void initDeviceRecord() {
@@ -83,13 +95,6 @@
      */
     public abstract String getId();
 
-    /**
-     * Transfer MediaDevice for media
-     *
-     * @return result of transfer media
-     */
-    public abstract boolean connect();
-
     void setConnectedRecord() {
         mConnectedRecord++;
         ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
@@ -97,11 +102,6 @@
     }
 
     /**
-     * Stop transfer MediaDevice
-     */
-    public abstract void disconnect();
-
-    /**
      * According the MediaDevice type to check whether we are connected to this MediaDevice.
      *
      * @return Whether it is connected.
@@ -162,6 +162,23 @@
     }
 
     /**
+     * Transfer MediaDevice for media
+     *
+     * @return result of transfer media
+     */
+    public boolean connect() {
+        setConnectedRecord();
+        mRouterManager.selectRoute(mPackageName, mRouteInfo);
+        return true;
+    }
+
+    /**
+     * Stop transfer MediaDevice
+     */
+    public void disconnect() {
+    }
+
+    /**
      * Rules:
      * 1. If there is one of the connected devices identified as a carkit, this carkit will
      * be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index 248b118..f341bf1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -32,6 +32,16 @@
     public static final String KEY_REMOTE_MEDIA = "remote_media";
 
     /**
+     * Key for the {@link android.media.session.MediaSession.Token}.
+     */
+    public static final String KEY_MEDIA_SESSION_TOKEN = "key_media_session_token";
+
+    /**
+     * Key for the {@link android.media.RoutingSessionInfo#getId()}
+     */
+    public static final String KEY_SESSION_INFO_ID = "key_session_info_id";
+
+    /**
      * Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media.
      */
     public static final String ACTION_MEDIA_OUTPUT =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index af91c34..166fbaa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -17,14 +17,11 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
 
 import com.android.settingslib.R;
-import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.HearingAidProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 
 /**
  * PhoneMediaDevice extends MediaDevice to represents Phone device.
@@ -35,15 +32,12 @@
 
     public static final String ID = "phone_media_device_id_1";
 
-    private LocalBluetoothProfileManager mProfileManager;
-    private LocalBluetoothManager mLocalBluetoothManager;
     private String mSummary = "";
 
-    PhoneMediaDevice(Context context, LocalBluetoothManager localBluetoothManager) {
-        super(context, MediaDeviceType.TYPE_PHONE_DEVICE);
+    PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+            String packageName) {
+        super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName);
 
-        mLocalBluetoothManager = localBluetoothManager;
-        mProfileManager = mLocalBluetoothManager.getProfileManager();
         initDeviceRecord();
     }
 
@@ -69,32 +63,6 @@
     }
 
     @Override
-    public boolean connect() {
-        final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
-        final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
-
-        // Some device may not have HearingAidProfile, consider all situation to set active device.
-        boolean isConnected = false;
-        if (hapProfile != null && a2dpProfile != null) {
-            isConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
-        } else if (a2dpProfile != null) {
-            isConnected = a2dpProfile.setActiveDevice(null);
-        } else if (hapProfile != null) {
-            isConnected = hapProfile.setActiveDevice(null);
-        }
-        updateSummary(isConnected);
-        setConnectedRecord();
-
-        Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
-        return isConnected;
-    }
-
-    @Override
-    public void disconnect() {
-        updateSummary(false);
-    }
-
-    @Override
     public boolean isConnected() {
         return true;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 44e70f4..954eb9b 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;
@@ -178,6 +181,7 @@
     static final String KEY_SCANRESULTS = "key_scanresults";
     static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
     static final String KEY_CONFIG = "key_config";
+    static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id";
     static final String KEY_FQDN = "key_fqdn";
     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
     static final String KEY_EAPTYPE = "eap_psktype";
@@ -214,7 +218,7 @@
     public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
 
     public static final String KEY_PREFIX_AP = "AP:";
-    public static final String KEY_PREFIX_FQDN = "FQDN:";
+    public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:";
     public static final String KEY_PREFIX_OSU = "OSU:";
 
     private final Context mContext;
@@ -247,6 +251,7 @@
      * Information associated with the {@link PasspointConfiguration}.  Only maintaining
      * the relevant info to preserve spaces.
      */
+    private String mPasspointUniqueId;
     private String mFqdn;
     private String mProviderFriendlyName;
     private boolean mIsRoaming = false;
@@ -305,6 +310,9 @@
                 mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
             }
         }
+        if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) {
+            mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID);
+        }
         if (savedState.containsKey(KEY_FQDN)) {
             mFqdn = savedState.getString(KEY_FQDN);
         }
@@ -348,6 +356,7 @@
      */
     public AccessPoint(Context context, PasspointConfiguration config) {
         mContext = context;
+        mPasspointUniqueId = config.getUniqueId();
         mFqdn = config.getHomeSp().getFqdn();
         mProviderFriendlyName = config.getHomeSp().getFriendlyName();
         mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeInMillis();
@@ -368,6 +377,7 @@
         mContext = context;
         networkId = config.networkId;
         mConfig = config;
+        mPasspointUniqueId = config.getKey();
         mFqdn = config.FQDN;
         setScanResultsPasspoint(homeScans, roamingScans);
         updateKey();
@@ -404,7 +414,7 @@
         if (isPasspoint()) {
             mKey = getKey(mConfig);
         } else if (isPasspointConfig()) {
-            mKey = getKey(mFqdn);
+            mKey = getKey(mPasspointUniqueId);
         } else if (isOsuProvider()) {
             mKey = getKey(mOsuProvider);
         } else { // Non-Passpoint AP
@@ -674,19 +684,19 @@
      */
     public static String getKey(WifiConfiguration config) {
         if (config.isPasspoint()) {
-            return getKey(config.FQDN);
+            return getKey(config.getKey());
         } else {
             return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
         }
     }
 
     /**
-     * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN.
+     * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier.
      */
-    public static String getKey(String fqdn) {
+    public static String getKey(String passpointUniqueId) {
         return new StringBuilder()
-                .append(KEY_PREFIX_FQDN)
-                .append(fqdn).toString();
+                .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID)
+                .append(passpointUniqueId).toString();
     }
 
     /**
@@ -763,7 +773,7 @@
 
     public boolean matches(WifiConfiguration config) {
         if (config.isPasspoint()) {
-            return (isPasspoint() && config.FQDN.equals(mConfig.FQDN));
+            return (isPasspoint() && config.getKey().equals(mConfig.getKey()));
         }
 
         if (!ssid.equals(removeDoubleQuotes(config.SSID))
@@ -1049,7 +1059,7 @@
     public String getConfigName() {
         if (mConfig != null && mConfig.isPasspoint()) {
             return mConfig.providerFriendlyName;
-        } else if (mFqdn != null) {
+        } else if (mPasspointUniqueId != null) {
             return mProviderFriendlyName;
         } else {
             return ssid;
@@ -1144,11 +1154,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()) {
@@ -1247,7 +1261,7 @@
      * Return true if this AccessPoint represents a Passpoint provider configuration.
      */
     public boolean isPasspointConfig() {
-        return mFqdn != null && mConfig == null;
+        return mPasspointUniqueId != null && mConfig == null;
     }
 
     /**
@@ -1303,8 +1317,12 @@
         if (info.isOsuAp() || mOsuStatus != null) {
             return (info.isOsuAp() && mOsuStatus != null);
         } else if (info.isPasspointAp() || isPasspoint()) {
+            // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API
+            //  is available
             return (info.isPasspointAp() && isPasspoint()
-                    && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN));
+                    && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)
+                    && TextUtils.equals(info.getPasspointProviderFriendlyName(),
+                    mConfig.providerFriendlyName));
         }
 
         if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -1370,6 +1388,9 @@
         if (mNetworkInfo != null) {
             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
         }
+        if (mPasspointUniqueId != null) {
+            savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId);
+        }
         if (mFqdn != null) {
             savedState.putString(KEY_FQDN, mFqdn);
         }
@@ -1942,11 +1963,11 @@
                 return;
             }
 
-            String fqdn = passpointConfig.getHomeSp().getFqdn();
+            String uniqueId = passpointConfig.getUniqueId();
             for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
                     wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) {
                 WifiConfiguration config = pairing.first;
-                if (TextUtils.equals(config.FQDN, fqdn)) {
+                if (TextUtils.equals(config.getKey(), uniqueId)) {
                     List<ScanResult> homeScans =
                             pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
                     List<ScanResult> roamingScans =
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index f21e466..2fb2481 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -84,7 +84,7 @@
         bundle.putParcelable(AccessPoint.KEY_NETWORKINFO, mNetworkInfo);
         bundle.putParcelable(AccessPoint.KEY_WIFIINFO, mWifiInfo);
         if (mFqdn != null) {
-            bundle.putString(AccessPoint.KEY_FQDN, mFqdn);
+            bundle.putString(AccessPoint.KEY_PASSPOINT_UNIQUE_ID, mFqdn);
         }
         if (mProviderFriendlyName != null) {
             bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 26abf71..586c154 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -606,7 +606,7 @@
 
             List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
 
-            // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
+            // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier.
             accessPoints.addAll(updatePasspointAccessPoints(
                     mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
 
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..6307caf5 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/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
index e0e2fd6..a39bcb7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -51,21 +51,7 @@
         when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
         when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
 
-        mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice);
-    }
-
-    @Test
-    public void connect_setActiveSuccess_isConnectedReturnTrue() {
-        when(mDevice.setActive()).thenReturn(true);
-
-        assertThat(mBluetoothMediaDevice.connect()).isTrue();
-    }
-
-    @Test
-    public void connect_setActiveFail_isConnectedReturnFalse() {
-        when(mDevice.setActive()).thenReturn(false);
-
-        assertThat(mBluetoothMediaDevice.connect()).isFalse();
+        mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null);
     }
 
     @Test
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..04ceb21 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
@@ -18,10 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 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 +36,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 +55,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);
@@ -90,9 +108,30 @@
     }
 
     @Test
-    public void connect_shouldSelectRoute() {
-        mInfoMediaDevice.connect();
+    public void getClientPackageName_returnPackageName() {
+        when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
 
-        verify(mRouterManager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo);
+        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/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 3726fb2..05f5fa0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -25,6 +25,9 @@
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,6 +48,8 @@
 
     @Mock
     private MediaRouter2Manager mRouterManager;
+    @Mock
+    private LocalBluetoothManager mLocalBluetoothManager;
 
     private InfoMediaManager mInfoMediaManager;
     private Context mContext;
@@ -54,7 +59,8 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
-        mInfoMediaManager = new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null);
+        mInfoMediaManager =
+                new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
         mInfoMediaManager.mRouterManager = mRouterManager;
     }
 
@@ -142,4 +148,59 @@
 
         assertThat(mInfoMediaManager.mMediaDevices).hasSize(0);
     }
+
+    @Test
+    public void onRoutesChanged_getAvailableRoutes_shouldAddMediaDevice() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        when(info.getId()).thenReturn(TEST_ID);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info);
+        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+
+        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+        assertThat(mediaDevice).isNull();
+
+        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+
+        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+    }
+
+    @Test
+    public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        when(info.getId()).thenReturn(TEST_ID);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info);
+        when(mRouterManager.getAllRoutes()).thenReturn(routes);
+
+        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+        assertThat(mediaDevice).isNull();
+
+        mInfoMediaManager.mPackageName = "";
+        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+
+        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+    }
+
+    @Test
+    public void connectDeviceWithoutPackageName_noSession_returnFalse() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, info,
+                TEST_PACKAGE_NAME);
+
+        final List<RoutingSessionInfo> infos = new ArrayList<>();
+
+        when(mRouterManager.getActiveSessions()).thenReturn(infos);
+
+        assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
+    }
 }
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..3d67ba0 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
@@ -80,7 +80,7 @@
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
         mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
-                mBluetoothMediaManager, mInfoMediaManager);
+                mInfoMediaManager, "com.test.packagename");
     }
 
     @Test
@@ -163,14 +163,14 @@
     }
 
     @Test
-    public void onDeviceAdded_mediaDeviceAndPhoneDeviceNotExistInList_addBothDevice() {
+    public void onDeviceAdded_addDevice() {
         final MediaDevice device = mock(MediaDevice.class);
 
         assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device);
 
-        assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
         verify(mCallback).onDeviceListUpdate(any());
     }
 
@@ -206,7 +206,7 @@
     }
 
     @Test
-    public void onDeviceListAdded_phoneDeviceNotExistInList_addPhoneDeviceAndDevicesList() {
+    public void onDeviceListAdded_addDevicesList() {
         final List<MediaDevice> devices = new ArrayList<>();
         final MediaDevice device1 = mock(MediaDevice.class);
         final MediaDevice device2 = mock(MediaDevice.class);
@@ -220,12 +220,12 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
 
-        assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
         verify(mCallback).onDeviceListUpdate(any());
     }
 
     @Test
-    public void onDeviceListAdded_phoneDeviceExistInList_addDeviceList() {
+    public void onDeviceListAdded_addDeviceList() {
         final List<MediaDevice> devices = new ArrayList<>();
         final MediaDevice device1 = mock(MediaDevice.class);
         final MediaDevice device2 = mock(MediaDevice.class);
@@ -245,12 +245,12 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
 
-        assertThat(mLocalMediaManager.mMediaDevices).hasSize(4);
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
         verify(mCallback).onDeviceListUpdate(any());
     }
 
     @Test
-    public void onDeviceRemoved_phoneDeviceIsLastDeviceAfterRemoveMediaDevice_removeBothDevice() {
+    public void onDeviceRemoved_removeDevice() {
         final MediaDevice device1 = mock(MediaDevice.class);
         mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
         mLocalMediaManager.mMediaDevices.add(device1);
@@ -260,7 +260,7 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device1);
 
-        assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+        assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
         verify(mCallback).onDeviceListUpdate(any());
     }
 
@@ -298,7 +298,7 @@
     }
 
     @Test
-    public void onDeviceListRemoved_phoneDeviceIsLastDeviceAfterRemoveDeviceList_removeAll() {
+    public void onDeviceListRemoved_removeAll() {
         final List<MediaDevice> devices = new ArrayList<>();
         final MediaDevice device1 = mock(MediaDevice.class);
         final MediaDevice device2 = mock(MediaDevice.class);
@@ -311,7 +311,8 @@
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
         mLocalMediaManager.registerCallback(mCallback);
-        mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices);
+        mLocalMediaManager.mMediaDeviceCallback
+                .onDeviceListRemoved(mLocalMediaManager.mMediaDevices);
 
         assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
         verify(mCallback).onDeviceListUpdate(any());
@@ -384,4 +385,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/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 02cb83e..fb8b78b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothClass;
@@ -83,6 +84,14 @@
     @Mock
     private MediaRoute2Info mRouteInfo3;
     @Mock
+    private MediaRoute2Info mBluetoothRouteInfo1;
+    @Mock
+    private MediaRoute2Info mBluetoothRouteInfo2;
+    @Mock
+    private MediaRoute2Info mBluetoothRouteInfo3;
+    @Mock
+    private MediaRoute2Info mPhoneRouteInfo;
+    @Mock
     private LocalBluetoothProfileManager mProfileManager;
     @Mock
     private HearingAidProfile mHapProfile;
@@ -90,6 +99,8 @@
     private A2dpProfile mA2dpProfile;
     @Mock
     private BluetoothDevice mDevice;
+    @Mock
+    private MediaRouter2Manager mMediaRouter2Manager;
 
     private BluetoothMediaDevice mBluetoothMediaDevice1;
     private BluetoothMediaDevice mBluetoothMediaDevice2;
@@ -100,7 +111,6 @@
     private InfoMediaDevice mInfoMediaDevice3;
     private List<MediaDevice> mMediaDevices = new ArrayList<>();
     private PhoneMediaDevice mPhoneMediaDevice;
-    private MediaRouter2Manager mMediaRouter2Manager;
 
     @Before
     public void setUp() {
@@ -133,17 +143,24 @@
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
         when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice);
 
-        mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1);
-        mBluetoothMediaDevice2 = new BluetoothMediaDevice(mContext, mCachedDevice2);
-        mBluetoothMediaDevice3 = new BluetoothMediaDevice(mContext, mCachedDevice3);
-        mMediaRouter2Manager = MediaRouter2Manager.getInstance(mContext);
+        mBluetoothMediaDevice1 =
+                new BluetoothMediaDevice(mContext, mCachedDevice1, mMediaRouter2Manager,
+                        mBluetoothRouteInfo1, TEST_PACKAGE_NAME);
+        mBluetoothMediaDevice2 =
+                new BluetoothMediaDevice(mContext, mCachedDevice2, mMediaRouter2Manager,
+                        mBluetoothRouteInfo2, TEST_PACKAGE_NAME);
+        mBluetoothMediaDevice3 =
+                new BluetoothMediaDevice(mContext, mCachedDevice3, mMediaRouter2Manager,
+                        mBluetoothRouteInfo3, TEST_PACKAGE_NAME);
         mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1,
                 TEST_PACKAGE_NAME);
         mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2,
                 TEST_PACKAGE_NAME);
         mInfoMediaDevice3 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo3,
                 TEST_PACKAGE_NAME);
-        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
+        mPhoneMediaDevice =
+                new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo,
+                        TEST_PACKAGE_NAME);
     }
 
     @Test
@@ -370,4 +387,11 @@
         assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1);
         assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2);
     }
+
+    @Test
+    public void connect_shouldSelectRoute() {
+        mInfoMediaDevice1.connect();
+
+        verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 0752dc0..db984fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -18,21 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.when;
-
-import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 
 import com.android.settingslib.R;
-import com.android.settingslib.bluetooth.A2dpProfile;
-import com.android.settingslib.bluetooth.HearingAidProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
@@ -40,17 +32,6 @@
 @RunWith(RobolectricTestRunner.class)
 public class PhoneMediaDeviceTest {
 
-    @Mock
-    private LocalBluetoothProfileManager mLocalProfileManager;
-    @Mock
-    private LocalBluetoothManager mLocalBluetoothManager;
-    @Mock
-    private HearingAidProfile mHapProfile;
-    @Mock
-    private A2dpProfile mA2dpProfile;
-    @Mock
-    private BluetoothDevice mDevice;
-
     private Context mContext;
     private PhoneMediaDevice mPhoneMediaDevice;
 
@@ -59,68 +40,8 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
-        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
-        when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
-        when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
-        when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice);
-
-        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
-    }
-
-    @Test
-    public void connect_phoneDeviceSetActiveSuccess_isConnectedReturnTrue() {
-        when(mA2dpProfile.setActiveDevice(null)).thenReturn(true);
-        when(mHapProfile.setActiveDevice(null)).thenReturn(true);
-
-        assertThat(mPhoneMediaDevice.connect()).isTrue();
-    }
-
-    @Test
-    public void connect_a2dpProfileSetActiveFail_isConnectedReturnFalse() {
-        when(mA2dpProfile.setActiveDevice(null)).thenReturn(false);
-        when(mHapProfile.setActiveDevice(null)).thenReturn(true);
-
-        assertThat(mPhoneMediaDevice.connect()).isFalse();
-    }
-
-    @Test
-    public void connect_hearingAidProfileSetActiveFail_isConnectedReturnFalse() {
-        when(mA2dpProfile.setActiveDevice(null)).thenReturn(true);
-        when(mHapProfile.setActiveDevice(null)).thenReturn(false);
-
-        assertThat(mPhoneMediaDevice.connect()).isFalse();
-    }
-
-    @Test
-    public void connect_hearingAidAndA2dpProfileSetActiveFail_isConnectedReturnFalse() {
-        when(mA2dpProfile.setActiveDevice(null)).thenReturn(false);
-        when(mHapProfile.setActiveDevice(null)).thenReturn(false);
-
-        assertThat(mPhoneMediaDevice.connect()).isFalse();
-    }
-
-    @Test
-    public void connect_hearingAidProfileIsNullAndA2dpProfileNotNull_isConnectedReturnTrue() {
-        when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null);
-
-        when(mA2dpProfile.setActiveDevice(null)).thenReturn(true);
-        assertThat(mPhoneMediaDevice.connect()).isTrue();
-    }
-
-    @Test
-    public void connect_hearingAidProfileNotNullAndA2dpProfileIsNull_isConnectedReturnTrue() {
-        when(mLocalProfileManager.getA2dpProfile()).thenReturn(null);
-
-        when(mHapProfile.setActiveDevice(null)).thenReturn(true);
-        assertThat(mPhoneMediaDevice.connect()).isTrue();
-    }
-
-    @Test
-    public void connect_hearingAidProfileAndA2dpProfileIsNull_isConnectedReturnFalse() {
-        when(mLocalProfileManager.getA2dpProfile()).thenReturn(null);
-        when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null);
-
-        assertThat(mPhoneMediaDevice.connect()).isFalse();
+        mPhoneMediaDevice =
+                new PhoneMediaDevice(mContext, null, null, null);
     }
 
     @Test
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..5946f21 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"/>
 
@@ -303,6 +308,22 @@
                   android:excludeFromRecents="true"
                   android:exported="false" />
 
+        <!--
+        The following is used as a no-op/null home activity when
+        no other MAIN/HOME activity is present (e.g., in CSI).
+        -->
+        <activity android:name=".NullHome"
+                  android:excludeFromRecents="true"
+                  android:label=""
+                  android:screenOrientation="nosensor">
+            <!-- The priority here is set to be lower than that for Settings -->
+            <intent-filter android:priority="-1100">
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <receiver
             android:name=".BugreportRequestedReceiver"
             android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml
new file mode 100644
index 0000000..5f9563a
--- /dev/null
+++ b/packages/Shell/res/layout/null_home_finishing_boot.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#80000000"
+    android:forceHasOverlappingRendering="false">
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="center"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="40sp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:text="@*android:string/android_start_title"/>
+        <ProgressBar
+            style="@android:style/Widget.Material.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12.75dp"
+            android:colorControlActivated="?android:attr/textColorPrimary"
+            android:indeterminate="true"/>
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 48d405a..1814593 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -97,6 +97,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -193,9 +194,7 @@
      * <p>
      * Must be a path supported by its FileProvider.
      */
-    // TODO: use the same variable for both dir
-    private static final String SCREENSHOT_DIR = "bugreports";
-    private static final String BUGREPORT_DIR = "/bugreports";
+    private static final String BUGREPORT_DIR = "bugreports";
 
     private static final String NOTIFICATION_CHANNEL_ID = "bugreports";
 
@@ -230,7 +229,7 @@
 
     private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
 
-    private File mScreenshotsDir;
+    private File mBugreportsDir;
 
     private BugreportManager mBugreportManager;
 
@@ -263,11 +262,12 @@
         mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
         startSelfIntent = new Intent(this, this.getClass());
 
-        mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
-        if (!mScreenshotsDir.exists()) {
-            Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
-            if (!mScreenshotsDir.mkdir()) {
-                Log.w(TAG, "Could not create directory " + mScreenshotsDir);
+        mBugreportsDir = new File(getFilesDir(), BUGREPORT_DIR);
+        if (!mBugreportsDir.exists()) {
+            Log.i(TAG, "Creating directory " + mBugreportsDir
+                    + " to store bugreports and screenshots");
+            if (!mBugreportsDir.mkdir()) {
+                Log.w(TAG, "Could not create directory " + mBugreportsDir);
             }
         }
         final Configuration conf = mContext.getResources().getConfiguration();
@@ -372,7 +372,7 @@
         @Override
         public void onFinished() {
             mInfo.renameBugreportFile();
-            mInfo.renameScreenshots(mScreenshotsDir);
+            mInfo.renameScreenshots();
             synchronized (mLock) {
                 sendBugreportFinishedBroadcastLocked();
             }
@@ -406,7 +406,7 @@
                         mInfo.bugreportFile);
             } else {
                 trackInfoWithIdLocked();
-                cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
+                cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
                 final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
                 intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
                 intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
@@ -418,7 +418,8 @@
 
     private static void sendRemoteBugreportFinishedBroadcast(Context context,
             String bugreportFileName, File bugreportFile) {
-        cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE);
+        cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE,
+                bugreportFile.getParentFile());
         final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
         final Uri bugreportUri = getUri(context, bugreportFile);
         final String bugreportHash = generateFileHash(bugreportFileName);
@@ -468,12 +469,12 @@
         return fileHash;
     }
 
-    static void cleanupOldFiles(final int minCount, final long minAge) {
+    static void cleanupOldFiles(final int minCount, final long minAge, File bugreportsDir) {
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
                 try {
-                    FileUtils.deleteOlderFiles(new File(BUGREPORT_DIR), minCount, minAge);
+                    FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge);
                 } catch (RuntimeException e) {
                     Log.e(TAG, "RuntimeException deleting old files", e);
                 }
@@ -604,18 +605,25 @@
         String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
 
         BugreportInfo info = new BugreportInfo(mContext, baseName, name,
-                shareTitle, shareDescription, bugreportType);
+                shareTitle, shareDescription, bugreportType, mBugreportsDir);
+        ParcelFileDescriptor bugreportFd;
+        ParcelFileDescriptor screenshotFd;
 
-        ParcelFileDescriptor bugreportFd = info.createBugreportFd();
-        if (bugreportFd == null) {
-            Log.e(TAG, "Bugreport parcel file descriptor is null.");
-            return;
-        }
-        ParcelFileDescriptor screenshotFd = info.createScreenshotFd();
-        if (screenshotFd == null) {
-            Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file");
-            FileUtils.closeQuietly(bugreportFd);
-            info.bugreportFile.delete();
+        try {
+            bugreportFd = info.createAndGetBugreportFd();
+            if (bugreportFd == null) {
+                Log.e(TAG, "Bugreport parcel file descriptor is null.");
+                return;
+            }
+            screenshotFd = info.createAndGetDefaultScreenshotFd();
+            if (screenshotFd == null) {
+                Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file");
+                FileUtils.closeQuietly(bugreportFd);
+                info.bugreportFile.delete();
+                return;
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error in generating bugreport files: ", e);
             return;
         }
         mBugreportManager = (BugreportManager) mContext.getSystemService(
@@ -639,21 +647,24 @@
         }
     }
 
-    private static ParcelFileDescriptor createReadWriteFile(File file) {
+    private static ParcelFileDescriptor getFd(File file) {
         try {
-            file.createNewFile();
-            file.setReadable(true, true);
-            file.setWritable(true, true);
-
-            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
+            return ParcelFileDescriptor.open(file,
                     ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
-            return fd;
-        } catch (IOException e) {
+        } catch (FileNotFoundException e) {
             Log.i(TAG, "Error in generating bugreports: ", e);
         }
         return null;
     }
 
+    private static void createReadWriteFile(File file) throws IOException {
+        if (!file.exists()) {
+            file.createNewFile();
+            file.setReadable(true, true);
+            file.setWritable(true, true);
+        }
+    }
+
     /**
      * Updates the system notification for a given bugreport.
      */
@@ -874,7 +885,7 @@
             return;
         }
         final String screenshotPath =
-                new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
+                new File(mBugreportsDir, info.getPathNextScreenshot()).getAbsolutePath();
 
         Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath)
                 .sendToTarget();
@@ -921,7 +932,7 @@
             info.addScreenshot(screenshotFile);
             if (info.finished) {
                 Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
-                info.renameScreenshots(mScreenshotsDir);
+                info.renameScreenshots();
                 sendBugreportNotification(info, mTakingScreenshot);
             }
             msg = mContext.getString(R.string.bugreport_screenshot_taken);
@@ -1030,11 +1041,10 @@
     /**
      * Build {@link Intent} that can be used to share the given bugreport.
      */
-    private static Intent buildSendIntent(Context context, BugreportInfo info,
-            File screenshotsDir) {
+    private static Intent buildSendIntent(Context context, BugreportInfo info) {
         // Rename files (if required) before sharing
         info.renameBugreportFile();
-        info.renameScreenshots(screenshotsDir);
+        info.renameScreenshots();
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
         final Uri bugreportUri;
@@ -1120,7 +1130,7 @@
 
         addDetailsToZipFile(info);
 
-        final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir);
+        final Intent sendIntent = buildSendIntent(mContext, info);
         if (sendIntent == null) {
             Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built");
             synchronized (mLock) {
@@ -1813,24 +1823,37 @@
          */
         BugreportInfo(Context context, String baseName, String name,
                 @Nullable String shareTitle, @Nullable String shareDescription,
-                @BugreportParams.BugreportMode int type) {
+                @BugreportParams.BugreportMode int type, File bugreportsDir) {
             this.context = context;
             this.name = this.initialName = name;
             this.shareTitle = shareTitle == null ? "" : shareTitle;
             this.shareDescription = shareDescription == null ? "" : shareDescription;
             this.type = type;
             this.baseName = baseName;
+            createBugreportFile(bugreportsDir);
+            createScreenshotFile(bugreportsDir);
         }
 
-        ParcelFileDescriptor createBugreportFd() {
-            bugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip"));
-            return createReadWriteFile(bugreportFile);
+        void createBugreportFile(File bugreportsDir) {
+            bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
         }
 
-        ParcelFileDescriptor createScreenshotFd() {
-            File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default"));
+        void createScreenshotFile(File bugreportsDir) {
+            File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
             addScreenshot(screenshotFile);
-            return createReadWriteFile(screenshotFile);
+        }
+
+        ParcelFileDescriptor createAndGetBugreportFd() throws IOException {
+            createReadWriteFile(bugreportFile);
+            return getFd(bugreportFile);
+        }
+
+        ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException {
+            if (screenshotFiles.isEmpty()) {
+                return null;
+            }
+            createReadWriteFile(screenshotFiles.get(0));
+            return getFd(screenshotFiles.get(0));
         }
 
         /**
@@ -1859,7 +1882,7 @@
          * Rename all screenshots files so that they contain the new {@code name} instead of the
          * {@code initialName} if user has changed it.
          */
-        void renameScreenshots(File screenshotDir) {
+        void renameScreenshots() {
             if (TextUtils.isEmpty(name)) {
                 return;
             }
@@ -1869,7 +1892,7 @@
                 final String newName = oldName.replaceFirst(initialName, name);
                 final File newFile;
                 if (!newName.equals(oldName)) {
-                    final File renamedFile = new File(screenshotDir, newName);
+                    final File renamedFile = new File(oldFile.getParentFile(), newName);
                     Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
                     newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
                 } else {
@@ -1889,7 +1912,8 @@
          * Rename bugreport file to include the name given by user via UI
          */
         void renameBugreportFile() {
-            File newBugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip"));
+            File newBugreportFile = new File(bugreportFile.getParentFile(),
+                    getFileName(this, ".zip"));
             if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
                 if (bugreportFile.renameTo(newBugreportFile)) {
                     bugreportFile = newBugreportFile;
diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java
new file mode 100644
index 0000000..bd97561
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/NullHome.java
@@ -0,0 +1,43 @@
+/*
+ * 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.shell;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * This covers the fallback case where no launcher is available.
+ * Usually Settings.apk has one fallback home activity.
+ * Settings.apk, however, is not part of CSI, which needs to be
+ * standalone (bootable and testable).
+ */
+public class NullHome extends Activity {
+    private static final String TAG = "NullHome";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentView(R.layout.null_home_finishing_boot);
+    }
+
+    protected void onDestroy() {
+        super.onDestroy();
+        Log.i(TAG, "onDestroy");
+    }
+}
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/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png
new file mode 100644
index 0000000..02de479
--- /dev/null
+++ b/packages/SystemUI/docs/QS-QQS.png
Binary files differ
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
new file mode 100644
index 0000000..b48ba67
--- /dev/null
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -0,0 +1,377 @@
+# Quick Settings Tiles (almost all there is to know about them)
+
+[TOC]
+
+## About this document
+
+This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics.
+
+## What are Quick Settings Tiles?
+
+Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text.
+
+Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages.
+
+The following image shows QQS on the left and QS on the right, with the tiles highlighted.
+
+![QQS on the left, QS on the right](QS-QQS.png)
+
+QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. 
+
+### A note on multi-user support
+
+All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users.
+
+For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users.
+
+## What are tiles made of?
+
+### Tile backend
+
+QS Tiles are composed of the following backend classes.
+
+* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles.
+  * `Icon`: Defines the basic interface for an icon as used by the tiles.
+  * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI.
+* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation.
+* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes:
+    * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device)
+    * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi).
+* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile)
+
+All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles.
+
+The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md)
+
+#### Tile State
+
+Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields:
+
+* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`.
+* **`icon`**; icon to display. It may depend on the current state.
+* **`label`**: usually the name of the tile.
+* **`secondaryLabel`**: text to display in a second line. Usually extra state information.
+* **`contentDescription`**
+* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`.
+* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility.
+
+Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it.
+
+Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`.
+
+#### SystemUI tiles
+
+Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.).
+
+For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile).
+
+### Tile views
+
+Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`.
+
+* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles.
+* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view:
+  * Holding the icon
+  * Background color and shape
+  * Ripple
+  * Click listening
+* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS.
+* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)**
+* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)**
+
+#### QSIconView and QSIconViewImpl
+
+`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles.
+
+This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes.
+
+Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon.
+
+### How are the backend and the views related?
+
+The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation.
+
+It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI.
+
+When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following:
+
+* Create the corresponding `QSTileView` to display in that container.
+* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS.
+
+#### Life of a tile click
+
+This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`.
+
+1. User clicks on tile. The following calls happen in sequence:
+   1. `QSTileBaseView#onClickListener`.
+   2. `QSTile#click`.
+   3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller.
+2. State in the device changes. This is normally outside of SystemUI's control.
+3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen:
+   1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state.
+   2. `QSTileImpl#handleRefreshState`
+4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller.
+5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`.
+6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view:
+   * The tile is rippled and the color changes to match the new state.
+   * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view.
+   * If the tile is a `QSTileView` (in expanded QS), the labels are changed.
+
+## Third party tiles (TileService)
+
+A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API.
+
+### API classes
+
+The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings).
+
+#### Tile
+
+Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields:
+
+* Label
+* Subtitle
+* Icon
+* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`)
+* Content description
+
+Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed.
+
+#### TileService
+
+This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user.
+
+The implementer is responsible for creating the methods that will respond to the following calls from SystemUI:
+
+* **`onTileAdded`**: called when the tile is added to QS.
+* **`onTileRemoved`**: called when the tile is removed from QS.
+* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object.
+* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`.
+* **`onClick`**: called when the user clicks on the tile.
+
+Additionally, the following final methods are provided:
+
+* ```java
+  public final Tile getQsTile()
+  ```
+
+  Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`.
+
+* ```java
+  public final boolean isLocked()
+
+  public final boolean isSecure()
+  ```
+
+  Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile.
+
+* ```java
+  public final void unlockAndRun(Runnable)
+  ```
+
+  May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`.
+
+* ```java
+  public final void showDialog(Dialog)
+  ```
+
+  Shows the provided dialog.
+
+##### Binding
+
+When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile.
+
+The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again.
+
+##### Active tile
+
+A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated.
+
+The tile will also be granted listening status if it's clicked by the user.
+
+### SystemUI classes
+
+The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/)
+
+#### CustomTile
+
+This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest.
+
+#### TileServices
+
+This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`.
+
+Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing.
+
+As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation.
+
+#### TileLifecycleManager
+
+This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable).
+
+The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound.
+
+Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user.
+
+#### TileServiceManager
+
+Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`.
+
+This class handles management of the service, including:
+
+* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`.
+* Relaying messages to the `TileService` through the `TileLifecycleManager`.
+* Determining the service's bind priority (to deal with OOM situations).
+* Detecting when the package/component has been removed in order to remove the tile and references to it.
+
+## How are tiles created/instantiated?
+
+This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes.
+
+### QSTileHost
+
+This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones.
+
+It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`).
+
+#### Tile specs
+
+Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise.
+
+SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`.
+
+### QSFactory
+
+This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles.
+
+In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`.
+
+#### QSFactoryImpl
+
+This class implements two methods as specified in the `QSFactory` interface:
+
+* ```java
+  public QSTile createTile(String)
+  ```
+
+  Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used.
+
+  If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available.
+
+* ```java
+  public QSTileView createTileView(QSTile, boolean)
+  ```
+
+  Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS).
+
+### Lifecycle of a Tile
+
+We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles.
+
+1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`).
+2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new".
+3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward.
+4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes.
+5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally:
+    * a callback is attached to the tile to communicate between the backend and the view or the panel.
+    * the click listeners in the tile are attached to those of the view.
+6. The tile view is added to the corresponding layout.
+
+When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to.
+
+#### Lifecycle of a CustomTile
+
+In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice).
+
+1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it.
+2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service.
+3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`.
+
+## Implementing a tile
+
+This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile.
+
+### Implementing a SystemUI tile
+
+1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter.
+2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers.
+3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind:
+    * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`).
+    * Changes to the tile state (either from controllers or from clicks) should call `refreshState`.
+    * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter.
+    * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers.
+    * Implement `isAvailable` so the tile will not be created when it's not necessary.
+4. In `QSFactoryImpl`:
+    * Inject a `Provider` for the tile created before.
+    * Add a case to the `switch` with a unique String spec for the chosen tile.
+5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles.
+
+#### Abstract methods in QSTileImpl
+
+Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`.
+
+* ```java
+    public TState newTileState()
+  ```
+
+    Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through).
+
+    If a tile has special behavior (no long click, no ripple), it can be set in its state here.
+
+* ```java
+    public void handleSetListening(boolean)
+    ```
+
+    Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java)
+
+* ```java
+    public QSIconView createTileView(Context)
+  ```
+
+  Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl`
+
+* ```java
+    public Intent getLongClickIntent()
+  ```
+
+  Determines the `Intent` launched when the Tile is long pressed.
+
+* ```java
+    protected void handleClick()
+
+    protected void handleSecondaryClick()
+
+    protected void handleLongClick()
+  ```
+
+  Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`.
+
+  By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`.
+
+* ```java
+    protected void handleUpdateState(TState, Object)
+  ```
+
+  Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI.
+
+* ```java
+  public int getMetricsCategory()
+  ```
+
+  Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile.
+
+* ```java
+  public boolean isAvailable()
+  ```
+
+  Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation.
+
+* ```java
+  public CharSequence getTileLabel()
+  ```
+
+  Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile.
+
+### Implementing a third party tile
+
+For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file
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-nodpi/android_11_dial.xml b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml
new file mode 100644
index 0000000..73fd37f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml
@@ -0,0 +1,63 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M77.773,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z"
+      android:fillColor="#F86734"/>
+  <path
+      android:pathData="M83.598,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z"
+      android:fillColor="#F86734"/>
+  <path
+      android:pathData="M70.044,75.974m-0.644,0a0.644,0.644 0,1 1,1.288 0a0.644,0.644 0,1 1,-1.288 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M56.896,80.985m-0.718,0a0.718,0.718 0,1 1,1.436 0a0.718,0.718 0,1 1,-1.436 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M43.408,78.881m-0.795,0a0.795,0.795 0,1 1,1.59 0a0.795,0.795 0,1 1,-1.59 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M32.419,70.115m-0.874,0a0.874,0.874 0,1 1,1.748 0a0.874,0.874 0,1 1,-1.748 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M27.306,56.992m-0.954,0a0.954,0.954 0,1 1,1.908 0a0.954,0.954 0,1 1,-1.908 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M29.313,43.489m-1.036,0a1.036,1.036 0,1 1,2.072 0a1.036,1.036 0,1 1,-2.072 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M37.988,32.445m-1.118,0a1.118,1.118 0,1 1,2.236 0a1.118,1.118 0,1 1,-2.236 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M51.137,27.064m-1.201,0a1.201,1.201 0,1 1,2.402 0a1.201,1.201 0,1 1,-2.402 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M64.553,28.868m-1.284,0a1.284,1.284 0,1 1,2.568 0a1.284,1.284 0,1 1,-2.568 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M75.522,37.652m-1.368,0a1.368,1.368 0,1 1,2.736 0a1.368,1.368 0,1 1,-2.736 0"
+      android:fillColor="#d7effe"/>
+  <path
+      android:pathData="M87.942,115.052l-47.557,-47.557l26.869,-26.87l47.557,47.558z">
+    <aapt:attr name="android:fillColor">
+      <gradient 
+          android:startY="56.087"
+          android:startX="55.8464"
+          android:endY="100.0297"
+          android:endX="99.7891"
+          android:type="linear">
+        <item android:offset="0" android:color="#3F000000"/>
+        <item android:offset="1" android:color="#00000000"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M53.928,54.17m-18.999,0a18.999,18.999 0,1 1,37.998 0a18.999,18.999 0,1 1,-37.998 0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M66.353,54.17m-3.185,0a3.185,3.185 0,1 1,6.37 0a3.185,3.185 0,1 1,-6.37 0"
+      android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 7a68c03..7f8d4fa 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -15,5 +15,5 @@
 -->
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/icon_bg"/>
-    <foreground android:drawable="@drawable/q"/>
+    <foreground android:drawable="@drawable/android_11_dial"/>
 </adaptive-icon>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
index 2a54dfa..31b2a7f 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
@@ -14,5 +14,5 @@
     limitations under the License.
 -->
 <color xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#77C360" />
+    android:color="#073042" />
 
diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml
deleted file mode 100644
index 0f42d2e..0000000
--- a/packages/SystemUI/res/drawable-nodpi/q.xml
+++ /dev/null
@@ -1,40 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="108dp"
-        android:height="108dp"
-        android:viewportWidth="108.0"
-        android:viewportHeight="108.0">
-    <group
-        android:name="scale"
-        android:pivotX="54" android:pivotY="54"
-        android:scaleX="0.9"
-        android:scaleY="0.9">
-        <group
-            android:name="nudge"
-            android:translateX="24"
-            android:translateY="23.5">
-            <path
-                android:name="tail"
-                android:fillColor="#FFFFFF"
-                android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/>
-            <path
-                android:name="counter"
-                android:fillColor="#FFFFFF"
-                android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/>
-        </group>
-    </group>
-</vector>
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/media/java/android/media/AudioDeviceAddress.aidl b/packages/SystemUI/res/layout/controls_icon.xml
similarity index 60%
copy from media/java/android/media/AudioDeviceAddress.aidl
copy to packages/SystemUI/res/layout/controls_icon.xml
index 6a1a7f7..cc46ced 100644
--- a/media/java/android/media/AudioDeviceAddress.aidl
+++ b/packages/SystemUI/res/layout/controls_icon.xml
@@ -1,4 +1,7 @@
-/* Copyright 2019, The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
@@ -12,7 +15,12 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
+-->
 
-package android.media;
-
-parcelable AudioDeviceAddress;
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="28dp"
+    android:layout_height="28dp"
+    android:scaleType="fitCenter"
+    android:layout_marginLeft="2dp"
+    android:layout_marginRight="2dp" />
diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml
index 096f1f4..3e0699d 100644
--- a/packages/SystemUI/res/layout/controls_no_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_no_favorites.xml
@@ -1,18 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+
 <merge
     xmlns:android="http://schemas.android.com/apk/res/android">
-  <TextView
-      android:id="@+id/controls_title"
-      android:text="@string/quick_controls_title"
+  <LinearLayout
+      android:id="@+id/controls_no_favorites_group"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
-      android:singleLine="true"
-      android:gravity="center"
-      android:textSize="25dp"
+      android:orientation="vertical"
       android:paddingTop="40dp"
       android:paddingBottom="40dp"
       android:layout_marginLeft="10dp"
       android:layout_marginRight="10dp"
-      android:textColor="@*android:color/foreground_material_dark"
-      android:fontFamily="@*android:string/config_headlineFontFamily"
-      android:background="@drawable/control_no_favorites_background"/>
+      android:background="@drawable/control_no_favorites_background">
+
+    <LinearLayout
+        android:id="@+id/controls_icon_row"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="horizontal"
+        android:paddingBottom="8dp" />
+
+    <TextView
+        android:id="@+id/controls_title"
+        android:text="@string/quick_controls_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:layout_gravity="center"
+        android:textSize="25sp"
+        android:textColor="@*android:color/foreground_material_dark"
+        android:fontFamily="@*android:string/config_headlineFontFamily" />
+  </LinearLayout>
 </merge>
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/config.xml b/packages/SystemUI/res/values/config.xml
index d72ce5e..8de2df5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -487,9 +487,6 @@
     <!-- Whether or not to add a "people" notifications section -->
     <bool name="config_usePeopleFiltering">false</bool>
 
-    <!-- Package name for controls plugin -->
-    <string name="config_controlsPluginPackageName" translatable="false">com.android.systemui.controls.panel</string>
-
     <!-- Defines the blacklist for system icons.  That is to say, the icons in the status bar that
          are part of the blacklist are never displayed. Each item in the blacklist must be a string
          defined in core/res/res/config.xml to properly blacklist the icon.
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9c997e8..7a3395c 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">150px</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 07a926f..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>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
index a378610..b8997c2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
@@ -135,8 +135,7 @@
         ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
         for (PluginInfo info : plugins) {
             if (className.startsWith(info.mPackage)) {
-                disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
-                disableAny = true;
+                disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
             }
         }
         return disableAny;
@@ -144,10 +143,11 @@
 
     public boolean disableAll() {
         ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
+        boolean disabledAny = false;
         for (int i = 0; i < plugins.size(); i++) {
-            disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
+            disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
         }
-        return plugins.size() != 0;
+        return disabledAny;
     }
 
     private boolean isPluginWhitelisted(ComponentName pluginName) {
@@ -166,7 +166,7 @@
         return false;
     }
 
-    private void disable(PluginInfo info, @PluginEnabler.DisableReason int reason) {
+    private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) {
         // Live by the sword, die by the sword.
         // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
 
@@ -176,10 +176,12 @@
         // assuming one of them must be bad.
         if (isPluginWhitelisted(pluginComponent)) {
             // Don't disable whitelisted plugins as they are a part of the OS.
-            return;
+            return false;
         }
         Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
         mManager.getPluginEnabler().setDisabled(pluginComponent, reason);
+
+        return true;
     }
 
     public <T> boolean dependsOn(Plugin p, Class<T> cls) {
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/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index f61f585..f48210c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -42,7 +42,6 @@
 import android.util.TypedValue;
 import android.view.View;
 import android.view.animation.Animation;
-import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -251,9 +250,9 @@
             SliceItem item = rc.getSliceItem();
             final Uri itemTag = item.getSlice().getUri();
             // Try to reuse the view if already exists in the layout
-            KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+            KeyguardSliceTextView button = mRow.findViewWithTag(itemTag);
             if (button == null) {
-                button = new KeyguardSliceButton(mContext);
+                button = new KeyguardSliceTextView(mContext);
                 button.setTextColor(blendedColor);
                 button.setTag(itemTag);
                 final int viewIndex = i - (mHasHeader ? 1 : 0);
@@ -316,8 +315,8 @@
         int childCount = mRow.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View v = mRow.getChildAt(i);
-            if (v instanceof Button) {
-                ((Button) v).setTextColor(blendedColor);
+            if (v instanceof TextView) {
+                ((TextView) v).setTextColor(blendedColor);
             }
         }
     }
@@ -500,8 +499,8 @@
             int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 View child = getChildAt(i);
-                if (child instanceof KeyguardSliceButton) {
-                    ((KeyguardSliceButton) child).setMaxWidth(width / childCount);
+                if (child instanceof KeyguardSliceTextView) {
+                    ((KeyguardSliceTextView) child).setMaxWidth(width / childCount);
                 }
             }
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -527,13 +526,13 @@
      * Representation of an item that appears under the clock on main keyguard message.
      */
     @VisibleForTesting
-    static class KeyguardSliceButton extends Button implements
+    static class KeyguardSliceTextView extends TextView implements
             ConfigurationController.ConfigurationListener {
 
         @StyleRes
         private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
 
-        public KeyguardSliceButton(Context context) {
+        KeyguardSliceTextView(Context context) {
             super(context, null /* attrs */, 0 /* styleAttr */, sStyleId);
             onDensityOrFontScaleChanged();
             setEllipsize(TruncateAt.END);
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..05838ab 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.bubbles;
 
-import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
@@ -44,19 +43,13 @@
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.RemoteInput;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ShortcutManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -75,25 +68,33 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController.BubbleExpandListener;
+import com.android.systemui.bubbles.BubbleController.BubbleStateChangeListener;
+import com.android.systemui.bubbles.BubbleController.NotifCallback;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PinnedStackListenerForwarder;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.io.FileDescriptor;
@@ -101,10 +102,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -116,7 +115,7 @@
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
 @Singleton
-public class BubbleController implements ConfigurationController.ConfigurationListener {
+public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
@@ -140,14 +139,13 @@
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
+    private final NotifPipeline mNotifPipeline;
     private final BubbleTaskStackListener mTaskStackListener;
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
     private final NotificationGroupManager mNotificationGroupManager;
     private final ShadeController mShadeController;
-    private final RemoteInputUriController mRemoteInputUriController;
-    private Handler mHandler = new Handler() {};
 
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
@@ -171,7 +169,6 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ZenModeController mZenModeController;
     private StatusBarStateListener mStatusBarStateListener;
-    private final ScreenshotHelper mScreenshotHelper;
 
     // Callback that updates BubbleOverflowActivity on data change.
     @Nullable private Runnable mOverflowCallback = null;
@@ -217,16 +214,6 @@
     }
 
     /**
-     * Listener for handling bubble screenshot events.
-     */
-    public interface BubbleScreenshotListener {
-        /**
-         * Called to trigger taking a screenshot and sending the result to a bubble.
-         */
-        void onBubbleScreenshot(Bubble bubble);
-    }
-
-    /**
      * Listener to be notified when a bubbles' notification suppression state changes.
      */
     public interface NotificationSuppressionChangedListener {
@@ -243,16 +230,17 @@
      */
     public interface NotifCallback {
         /**
-         * Called when the BubbleController wants to remove an entry that it was previously hiding
-         * from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}.
+         * Called when a bubbled notification that was hidden from the shade is now being removed
+         * This can happen when an app cancels a bubbled notification or when the user dismisses a
+         * bubble.
          */
-        void removeNotification(NotificationEntry entry);
+        void removeNotification(NotificationEntry entry, int reason);
 
         /**
          * Called when a bubbled notification has changed whether it should be
          * filtered from the shade.
          */
-        void invalidateNotificationFilter(String reason);
+        void invalidateNotifications(String reason);
 
         /**
          * Called on a bubbled entry that has been removed when there are no longer
@@ -301,11 +289,13 @@
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManager groupManager,
             NotificationEntryManager entryManager,
-            RemoteInputUriController remoteInputUriController) {
+            NotifPipeline notifPipeline,
+            FeatureFlags featureFlags,
+            DumpController dumpController) {
         this(context, notificationShadeWindowController, statusBarStateController, shadeController,
                 data, null /* synchronizer */, configurationController, interruptionStateProvider,
                 zenModeController, notifUserManager, groupManager, entryManager,
-                remoteInputUriController);
+                notifPipeline, featureFlags, dumpController);
     }
 
     public BubbleController(Context context,
@@ -320,13 +310,15 @@
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManager groupManager,
             NotificationEntryManager entryManager,
-            RemoteInputUriController remoteInputUriController) {
+            NotifPipeline notifPipeline,
+            FeatureFlags featureFlags,
+            DumpController dumpController) {
+        dumpController.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
-        mRemoteInputUriController = remoteInputUriController;
         mZenModeController.addCallback(new ZenModeController.Callback() {
             @Override
             public void onZenChanged(int zen) {
@@ -364,7 +356,13 @@
 
         mNotificationEntryManager = entryManager;
         mNotificationGroupManager = groupManager;
-        setupNEM();
+        mNotifPipeline = notifPipeline;
+
+        if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
+            setupNEM();
+        } else {
+            setupNotifPipeline();
+        }
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
@@ -399,7 +397,6 @@
         mUserCreatedBubbles = new HashSet<>();
         mUserBlockedBubbles = new HashSet<>();
 
-        mScreenshotHelper = new ScreenshotHelper(context);
         mBubbleIconFactory = new BubbleIconFactory(context);
     }
 
@@ -424,6 +421,14 @@
                     }
 
                     @Override
+                    public void onEntryRemoved(
+                            NotificationEntry entry,
+                            @android.annotation.Nullable NotificationVisibility visibility,
+                            boolean removedByUser) {
+                        BubbleController.this.onEntryRemoved(entry);
+                    }
+
+                    @Override
                     public void onNotificationRankingUpdated(RankingMap rankingMap) {
                         onRankingUpdated(rankingMap);
                     }
@@ -433,8 +438,29 @@
                 new NotificationRemoveInterceptor() {
                     @Override
                     public boolean onNotificationRemoveRequested(
-                            String key, NotificationEntry entry, int reason) {
-                        return shouldInterceptDismissal(entry, reason);
+                            String key,
+                            NotificationEntry entry,
+                            int dismissReason) {
+                        final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
+                        final boolean isUserDimiss = dismissReason == REASON_CANCEL
+                                || dismissReason == REASON_CLICK;
+                        final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
+                                || dismissReason == REASON_APP_CANCEL_ALL;
+                        final boolean isSummaryCancel =
+                                dismissReason == REASON_GROUP_SUMMARY_CANCELED;
+
+                        // Need to check for !appCancel here because the notification may have
+                        // previously been dismissed & entry.isRowDismissed would still be true
+                        boolean userRemovedNotif =
+                                (entry != null && entry.isRowDismissed() && !isAppCancel)
+                                || isClearAll || isUserDimiss || isSummaryCancel;
+
+                        if (userRemovedNotif || isUserCreatedBubble(key)
+                                || isSummaryOfUserCreatedBubble(entry)) {
+                            return handleDismissalInterception(entry);
+                        }
+
+                        return false;
                     }
                 });
 
@@ -458,13 +484,13 @@
 
         addNotifCallback(new NotifCallback() {
             @Override
-            public void removeNotification(NotificationEntry entry) {
+            public void removeNotification(NotificationEntry entry, int reason) {
                 mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
-                        UNDEFINED_DISMISS_REASON);
+                        reason);
             }
 
             @Override
-            public void invalidateNotificationFilter(String reason) {
+            public void invalidateNotifications(String reason) {
                 mNotificationEntryManager.updateNotifications(reason);
             }
 
@@ -472,18 +498,28 @@
             public void maybeCancelSummary(NotificationEntry entry) {
                 // Check if removed bubble has an associated suppressed group summary that needs
                 // to be removed now.
-                final String groupKey = entry.getSbn().getGroup();
+                final String groupKey = entry.getSbn().getGroupKey();
                 if (mBubbleData.isSummarySuppressed(groupKey)) {
-                    mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey());
+                    mBubbleData.removeSuppressedSummary(groupKey);
 
                     final NotificationEntry summary =
                             mNotificationEntryManager.getActiveNotificationUnfiltered(
                                     mBubbleData.getSummaryKey(groupKey));
-                    mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
-                            UNDEFINED_DISMISS_REASON);
+                    if (summary != null) {
+                        mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
+                                UNDEFINED_DISMISS_REASON);
+                    }
                 }
 
-                // Check if summary should be removed from NoManGroup
+                // Check if we still need to remove the summary from NoManGroup because the summary
+                // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
+                // For example:
+                // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
+                // 2. User expands bubbles so now their respective notifications in the shade are
+                // hidden, including the group summary
+                // 3. User removes all bubbles
+                // 4. We expect all the removed bubbles AND the summary (note: the summary was
+                // never added to the suppressedSummary list in BubbleData, so we add this check)
                 NotificationEntry summary =
                         mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn());
                 if (summary != null) {
@@ -500,6 +536,31 @@
         });
     }
 
+    private void setupNotifPipeline() {
+        mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+            @Override
+            public void onEntryAdded(NotificationEntry entry) {
+                BubbleController.this.onEntryAdded(entry);
+            }
+
+            @Override
+            public void onEntryUpdated(NotificationEntry entry) {
+                BubbleController.this.onEntryUpdated(entry);
+            }
+
+            @Override
+            public void onRankingUpdate(RankingMap rankingMap) {
+                onRankingUpdated(rankingMap);
+            }
+
+            @Override
+            public void onEntryRemoved(NotificationEntry entry,
+                    @NotifCollection.CancellationReason int reason) {
+                BubbleController.this.onEntryRemoved(entry);
+            }
+        });
+    }
+
     /**
      * Sets whether to perform inflation on the same thread as the caller. This method should only
      * be used in tests, not in production.
@@ -536,9 +597,6 @@
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
-            if (mBubbleScreenshotListener != null) {
-                mStackView.setBubbleScreenshotListener(mBubbleScreenshotListener);
-            }
         }
     }
 
@@ -783,7 +841,7 @@
             Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
         }
         entry.setFlagBubble(false);
-        removeBubble(entry.getKey(), DISMISS_BLOCKED);
+        removeBubble(entry, DISMISS_BLOCKED);
         mUserCreatedBubbles.remove(entry.getKey());
         if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
                 mContext, entry.getSbn().getPackageName())) {
@@ -800,17 +858,29 @@
         return mUserCreatedBubbles.contains(key);
     }
 
+    boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) {
+        if (isSummaryOfBubbles(entry)) {
+            List<Bubble> bubbleChildren =
+                    mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey());
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                // Check if any are user-created (i.e. experimental bubbles)
+                if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
-     * Removes the bubble associated with the {@param uri}.
+     * Removes the bubble with the given NotificationEntry.
      * <p>
      * Must be called from the main thread.
      */
     @MainThread
-    void removeBubble(String key, int reason) {
-        // TEMP: refactor to change this to pass entry
-        Bubble bubble = mBubbleData.getBubbleWithKey(key);
-        if (bubble != null) {
-            mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
+    void removeBubble(NotificationEntry entry, int reason) {
+        if (mBubbleData.hasBubbleWithKey(entry.getKey())) {
+            mBubbleData.notificationEntryRemoved(entry, reason);
         }
     }
 
@@ -840,7 +910,7 @@
                 && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
         if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
             // It was previously a bubble but no longer a bubble -- lets remove it
-            removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
+            removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
         } else if (shouldBubble) {
             if (wasAdjusted && !previouslyUserCreated) {
                 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
@@ -850,6 +920,21 @@
         }
     }
 
+    private void onEntryRemoved(NotificationEntry entry) {
+        if (isSummaryOfBubbles(entry)) {
+            final String groupKey = entry.getSbn().getGroupKey();
+            mBubbleData.removeSuppressedSummary(groupKey);
+
+            // Remove any associated bubble children with the summary
+            final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED);
+            }
+        } else {
+            removeBubble(entry, DISMISS_NOTIF_CANCEL);
+        }
+    }
+
     private void onRankingUpdated(RankingMap rankingMap) {
         // Forward to BubbleData to block any bubbles which should no longer be shown
         mBubbleData.notificationRankingUpdated(rankingMap);
@@ -877,7 +962,6 @@
                 final Bubble bubble = removed.first;
                 @DismissReason final int reason = removed.second;
                 mStackView.removeBubble(bubble);
-
                 // If the bubble is removed for user switching, leave the notification in place.
                 if (reason != DISMISS_USER_CHANGED) {
                     if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
@@ -885,7 +969,7 @@
                         // The bubble is now gone & the notification is hidden from the shade, so
                         // time to actually remove it
                         for (NotifCallback cb : mCallbacks) {
-                            cb.removeNotification(bubble.getEntry());
+                            cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
                         }
                     } else {
                         // Update the flag for SysUI
@@ -939,7 +1023,7 @@
             }
 
             for (NotifCallback cb : mCallbacks) {
-                cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate");
+                cb.invalidateNotifications("BubbleData.Listener.applyUpdate");
             }
             updateStack();
 
@@ -961,124 +1045,85 @@
     };
 
     /**
-     * We intercept notification entries cancelled by the user (i.e. dismissed) when there is an
-     * active bubble associated with it. We do this so that developers can still cancel it
-     * (and hence the bubbles associated with it). However, these intercepted notifications
-     * should then be hidden from the shade since the user has cancelled them, so we update
-     * {@link Bubble#showInShade}.
+     * We intercept notification entries (including group summaries) dismissed by the user when
+     * there is an active bubble associated with it. We do this so that developers can still
+     * cancel it (and hence the bubbles associated with it). However, these intercepted
+     * notifications should then be hidden from the shade since the user has cancelled them, so we
+     *  {@link Bubble#setSuppressNotification}.  For the case of suppressed summaries, we also add
+     *  {@link BubbleData#addSummaryToSuppress}.
      *
-     * 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) {
+    public boolean handleDismissalInterception(NotificationEntry entry) {
         if (entry == null) {
             return false;
         }
-        String key = entry.getKey();
-        String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
-        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
 
-        boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
-        boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
-                && mBubbleData.getSummaryKey(groupKey).equals(key));
-        boolean isSummary = entry != null
-                && entry.getSbn().getNotification().isGroupSummary();
-        boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
-                && bubbleChildren != null && !bubbleChildren.isEmpty();
+        final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey())
+                && entry.isBubble();
+        final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry);
 
-        if (!inBubbleData && !isSummaryOfBubbles) {
-            return false;
-        }
-
-        final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
-        final boolean isUserDimiss = dismissReason == REASON_CANCEL
-                || dismissReason == REASON_CLICK;
-        final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
-                || dismissReason == REASON_APP_CANCEL_ALL;
-        final boolean isSummaryCancel = dismissReason == REASON_GROUP_SUMMARY_CANCELED;
-
-        // Need to check for !appCancel here because the notification may have
-        // previously been dismissed & entry.isRowDismissed would still be true
-        boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
-                || isClearAll || isUserDimiss || isSummaryCancel;
-        if (isSummaryOfBubbles) {
-            return handleSummaryRemovalInterception(entry, userRemovedNotif);
-        }
-
-        // 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;
-        if (bubbleExtended) {
+        if (interceptSummaryDismissal) {
+            handleSummaryDismissalInterception(entry);
+        } else if (interceptBubbleDismissal) {
+            Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey());
             bubble.setSuppressNotification(true);
             bubble.setShowDot(false /* show */, true /* animate */);
-            for (NotifCallback cb : mCallbacks) {
-                cb.invalidateNotificationFilter("BubbleController"
-                        + ".shouldInterceptDismissal");
-            }
-            return true;
-        } else if (!userRemovedNotif && entry != null
-                && !isUserCreatedBubble(bubble.getKey())) {
-            // This wasn't a user removal so we should remove the bubble as well
-            mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
+        } else {
             return false;
         }
-        return false;
+
+        // Update the shade
+        for (NotifCallback cb : mCallbacks) {
+            cb.invalidateNotifications("BubbleController.handleDismissalInterception");
+        }
+        return true;
     }
 
-    private boolean handleSummaryRemovalInterception(NotificationEntry summary,
-            boolean userRemovedNotif) {
-        String groupKey = summary.getSbn().getGroupKey();
-        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
-
-        if (userRemovedNotif) {
-            // If it's a user dismiss we mark the children to be hidden from the shade.
-            for (int i = 0; i < bubbleChildren.size(); i++) {
-                Bubble bubbleChild = bubbleChildren.get(i);
-                // As far as group manager is concerned, once a child is no longer shown
-                // in the shade, it is essentially removed.
-                mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
-                bubbleChild.setSuppressNotification(true);
-                bubbleChild.setShowDot(false /* show */, true /* animate */);
-            }
-            // And since all children are removed, remove the summary.
-            mNotificationGroupManager.onEntryRemoved(summary);
-
-            // If the summary was auto-generated we don't need to keep that notification around
-            // because apps can't cancel it; so we only intercept & suppress real summaries.
-            boolean isAutogroupSummary = (summary.getSbn().getNotification().flags
-                    & FLAG_AUTOGROUP_SUMMARY) != 0;
-            if (!isAutogroupSummary) {
-                // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
-                mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
-                        summary.getKey());
-                // Tell shade to update for the suppression
-                mNotificationEntryManager.updateNotifications("BubbleController"
-                        + ".handleSummaryRemovalInterception");
-            }
-            return !isAutogroupSummary;
-        } else {
-            // If it's not a user dismiss it's a cancel.
-            for (int i = 0; i < bubbleChildren.size(); i++) {
-                // First check if any of these are user-created (i.e. experimental bubbles)
-                if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
-                    // Experimental bubble! Intercept the removal.
-                    return true;
-                }
-            }
-
-            // Not an experimental bubble, safe to remove.
-            mBubbleData.removeSuppressedSummary(groupKey);
-            // Remove any associated bubble children with the summary.
-            for (int i = 0; i < bubbleChildren.size(); i++) {
-                Bubble bubbleChild = bubbleChildren.get(i);
-                mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
-                        DISMISS_GROUP_CANCELLED);
-            }
+    private boolean isSummaryOfBubbles(NotificationEntry entry) {
+        if (entry == null) {
             return false;
         }
+
+        String groupKey = entry.getSbn().getGroupKey();
+        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+        boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
+                && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
+        boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
+        return (isSuppressedSummary || isSummary)
+                && bubbleChildren != null
+                && !bubbleChildren.isEmpty();
+    }
+
+    private void handleSummaryDismissalInterception(NotificationEntry summary) {
+        // current children in the row:
+        final List<NotificationEntry> children = summary.getChildren();
+        if (children != null) {
+            for (int i = 0; i < children.size(); i++) {
+                NotificationEntry child = children.get(i);
+                if (mBubbleData.hasBubbleWithKey(child.getKey())) {
+                    // Suppress the bubbled child
+                    // As far as group manager is concerned, once a child is no longer shown
+                    // in the shade, it is essentially removed.
+                    Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey());
+                    mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
+                    bubbleChild.setSuppressNotification(true);
+                    bubbleChild.setShowDot(false /* show */, true /* animate */);
+                } else {
+                    // non-bubbled children can be removed
+                    for (NotifCallback cb : mCallbacks) {
+                        cb.removeNotification(child, REASON_GROUP_SUMMARY_CANCELED);
+                    }
+                }
+            }
+        }
+
+        // And since all children are removed, remove the summary.
+        mNotificationGroupManager.onEntryRemoved(summary);
+
+        // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
+        mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
+                summary.getKey());
     }
 
     /**
@@ -1267,68 +1312,4 @@
             }
         }
     }
-
-    // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
-    private Intent prepareRemoteInputFromData(String contentType, Uri data,
-            RemoteInput remoteInput, NotificationEntry entry) {
-        HashMap<String, Uri> results = new HashMap<>();
-        results.put(contentType, data);
-        mRemoteInputUriController.grantInlineReplyUriPermission(entry.getSbn(), data);
-        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results);
-
-        return fillInIntent;
-    }
-
-    // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
-    private void sendRemoteInput(Intent intent, NotificationEntry entry,
-            PendingIntent pendingIntent) {
-        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
-        // will reset the throttling for this package.
-        // Strictly speaking, the intent receiver may be different from the notification publisher,
-        // but that's an edge case, and also because we can't always know which package will receive
-        // an intent, so we just reset for the publisher.
-        mContext.getSystemService(ShortcutManager.class).onApplicationActive(
-                entry.getSbn().getPackageName(),
-                entry.getSbn().getUser().getIdentifier());
-
-        try {
-            pendingIntent.send(mContext, 0, intent);
-        } catch (PendingIntent.CanceledException e) {
-            Log.i(TAG, "Unable to send remote input result", e);
-        }
-    }
-
-    private void sendScreenshotToBubble(Bubble bubble) {
-        mScreenshotHelper.takeScreenshot(
-                android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true /* hasStatus */,
-                true /* hasNav */,
-                mHandler,
-                new Consumer<Uri>() {
-                    @Override
-                    public void accept(Uri uri) {
-                        if (uri != null) {
-                            NotificationEntry entry = bubble.getEntry();
-                            Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
-                                    .getNotification().findRemoteInputActionPair(false);
-                            if (pair != null) {
-                                RemoteInput remoteInput = pair.first;
-                                Notification.Action action = pair.second;
-                                Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
-                                        remoteInput, entry);
-                                sendRemoteInput(dataIntent, entry, action.actionIntent);
-                                mBubbleData.setSelectedBubble(bubble);
-                                mBubbleData.setExpanded(true);
-                            } else {
-                                Log.w(TAG, "No RemoteInput found for notification: "
-                                        + entry.getSbn().getKey());
-                            }
-                        }
-                    }
-                });
-    }
-
-    private final BubbleScreenshotListener mBubbleScreenshotListener =
-            bubble -> sendScreenshotToBubble(bubble);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 50a5063..0d5261d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -19,9 +19,9 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.view.Display.INVALID_DISPLAY;
-
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.sNewInsetsMode;
+
 import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -56,6 +56,7 @@
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
@@ -146,7 +147,7 @@
                             // the bubble again so we'll just remove it.
                             Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                                     + ", " + e.getMessage() + "; removing bubble");
-                            mBubbleController.removeBubble(getBubbleKey(),
+                            mBubbleController.removeBubble(getBubbleEntry(),
                                     BubbleController.DISMISS_INVALID_INTENT);
                         }
                     });
@@ -190,7 +191,7 @@
             }
             if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+                post(() -> mBubbleController.removeBubble(mBubble.getEntry(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
@@ -279,6 +280,10 @@
         return mBubble != null ? mBubble.getKey() : "null";
     }
 
+    private NotificationEntry getBubbleEntry() {
+        return mBubble != null ? mBubble.getEntry() : null;
+    }
+
     void applyThemeAttrs() {
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 006de84..20b3386 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -73,9 +73,6 @@
 
     private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
 
-    private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu";
-    private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false;
-
     private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
     private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
 
@@ -137,16 +134,6 @@
      * When true, show a menu when a bubble is long-pressed, which will allow the user to take
      * actions on that bubble.
      */
-    static boolean allowBubbleScreenshotMenu(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ALLOW_BUBBLE_MENU,
-                ALLOW_BUBBLE_MENU_DEFAULT ? 1 : 0) != 0;
-    }
-
-    /**
-     * When true, show a menu when a bubble is long-pressed, which will allow the user to take
-     * actions on that bubble.
-     */
     static boolean allowBubbleOverflow(Context context) {
         return Settings.Secure.getInt(context.getContentResolver(),
                 ALLOW_BUBBLE_OVERFLOW,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
deleted file mode 100644
index bf83065..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.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.bubbles;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-/**
- * Menu which allows users to take actions on bubbles, ex. screenshots.
- */
-public class BubbleMenuView extends FrameLayout {
-    private FrameLayout mMenu;
-    private boolean mShowing = false;
-
-    /** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/
-    public static final long SCREENSHOT_DELAY = 200;
-
-    public BubbleMenuView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public BubbleMenuView(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mMenu = findViewById(R.id.bubble_menu_view);
-        ImageView icon = findViewById(com.android.internal.R.id.icon);
-        icon.setImageDrawable(mContext.getDrawable(com.android.internal.R.drawable.ic_screenshot));
-    }
-
-    /**
-     * Get the bubble menu view.
-     */
-    public View getMenuView() {
-        return mMenu;
-    }
-
-    /**
-     * Checks whether the bubble menu is currently displayed.
-     */
-    public boolean isShowing() {
-        return mShowing;
-    }
-
-    /**
-     * Show the bubble menu at the specified position on the screen.
-     */
-    public void show(float x, float y) {
-        mShowing = true;
-        this.setVisibility(VISIBLE);
-        mMenu.setTranslationX(x);
-        mMenu.setTranslationY(y);
-    }
-
-    /**
-     * Hide the bubble menu.
-     */
-    public void hide() {
-        mShowing = false;
-        this.setVisibility(GONE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 6062a3d..bce172b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -115,7 +115,6 @@
     /** How long to wait, in milliseconds, before hiding the flyout. */
     @VisibleForTesting
     static final int FLYOUT_HIDE_AFTER = 5000;
-    private BubbleController.BubbleScreenshotListener mBubbleScreenshotListener;
 
     /**
      * Interface to synchronize {@link View} state and the screen.
@@ -169,7 +168,6 @@
     private ExpandedAnimationController mExpandedAnimationController;
 
     private FrameLayout mExpandedViewContainer;
-    @Nullable private BubbleMenuView mBubbleMenuView;
 
     private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
@@ -516,9 +514,6 @@
             mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
             mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
         });
-
-        mInflater.inflate(R.layout.bubble_menu_view, this);
-        mBubbleMenuView = findViewById(R.id.bubble_menu_container);
     }
 
     private void setUpOverflow() {
@@ -533,10 +528,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 */);
@@ -742,13 +733,6 @@
     }
 
     /**
-     * Sets the screenshot listener.
-     */
-    public void setBubbleScreenshotListener(BubbleController.BubbleScreenshotListener listener) {
-        mBubbleScreenshotListener = listener;
-    }
-
-    /**
      * Whether the stack of bubbles is expanded or not.
      */
     public boolean isExpanded() {
@@ -856,6 +840,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
@@ -942,14 +930,12 @@
     public View getTargetView(MotionEvent event) {
         float x = event.getRawX();
         float y = event.getRawY();
-        if (mBubbleMenuView.isShowing()) {
-            if (isIntersecting(mBubbleMenuView.getMenuView(), x, y)) {
-                return mBubbleMenuView;
-            }
-            return null;
-        }
         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);
@@ -1164,7 +1150,6 @@
             }
             return;
         }
-        hideBubbleMenu();
         mStackAnimationController.cancelStackPositionAnimations();
         mBubbleContainer.setActiveController(mStackAnimationController);
         hideFlyoutImmediate();
@@ -1566,10 +1551,6 @@
     @Override
     public void getBoundsOnScreen(Rect outRect) {
         // If the bubble menu is open, the entire screen should capture touch events.
-        if (mBubbleMenuView.isShowing()) {
-            outRect.set(0, 0, getWidth(), getHeight());
-            return;
-        }
         if (!mIsExpanded) {
             if (getBubbleCount() > 0) {
                 mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
@@ -1804,50 +1785,4 @@
         }
         return bubbles;
     }
-
-    /**
-     * Show the bubble menu, positioned relative to the stack.
-     */
-    public void showBubbleMenu() {
-        PointF currentPos = mStackAnimationController.getStackPosition();
-        mBubbleMenuView.setVisibility(View.INVISIBLE);
-        post(() -> {
-            float yPos = currentPos.y;
-            float xPos = currentPos.x;
-            if (mStackAnimationController.isStackOnLeftSide()) {
-                xPos += mBubbleSize;
-            } else {
-                xPos -= mBubbleMenuView.getMenuView().getWidth();
-            }
-
-            mBubbleMenuView.show(xPos, yPos);
-        });
-    }
-
-    /**
-     * Hide the bubble menu.
-     */
-    public void hideBubbleMenu() {
-        mBubbleMenuView.hide();
-    }
-
-    /**
-     * Determines whether the bubble menu is currently showing.
-     */
-    public boolean isShowingBubbleMenu() {
-        return mBubbleMenuView.isShowing();
-    }
-
-    /**
-     * Take a screenshot and send it to the specified bubble.
-     */
-    public void sendScreenshotToBubble(Bubble bubble) {
-        hideBubbleMenu();
-        // delay allows the bubble menu to disappear before the screenshot
-        // done here because we already have a Handler to delay with.
-        // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
-        postDelayed(() -> {
-            mBubbleScreenshotListener.onBubbleScreenshot(bubble);
-        }, BubbleMenuView.SCREENSHOT_DELAY);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index fdeaf1f..645696d0 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,
@@ -57,14 +58,12 @@
     private final PointF mViewPositionOnTouchDown = new PointF();
     private final BubbleStackView mStack;
     private final BubbleData mBubbleData;
-    private final Context mContext;
 
     private BubbleController mController = Dependency.get(BubbleController.class);
 
     private boolean mMovedEnough;
     private int mTouchSlopSquared;
     private VelocityTracker mVelocityTracker;
-    private Runnable mShowBubbleMenuRunnable;
 
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
@@ -77,7 +76,6 @@
         mTouchSlopSquared = touchSlop * touchSlop;
         mBubbleData = bubbleData;
         mStack = stackView;
-        mContext = context;
     }
 
     @Override
@@ -94,24 +92,19 @@
         // anything, collapse the stack.
         if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
             mBubbleData.setExpanded(false);
-            mStack.hideBubbleMenu();
             resetForNextGesture();
             return false;
         }
 
-        if (mTouchedView instanceof BubbleMenuView) {
-            mStack.hideBubbleMenu();
-            resetForNextGesture();
-            mStack.sendScreenshotToBubble(mBubbleData.getSelectedBubble());
-            return false;
-        }
-
         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();
             resetForNextGesture();
             return false;
         }
@@ -134,12 +127,6 @@
                 if (isStack) {
                     mViewPositionOnTouchDown.set(mStack.getStackPosition());
                     mStack.onDragStart();
-                    if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded()
-                            && BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) {
-                        mShowBubbleMenuRunnable = mStack::showBubbleMenu;
-                        mStack.postDelayed(mShowBubbleMenuRunnable,
-                                ViewConfiguration.getLongPressTimeout());
-                    }
                 } else if (isFlyout) {
                     mStack.onFlyoutDragStart();
                 } else {
@@ -150,10 +137,6 @@
 
                 break;
             case MotionEvent.ACTION_MOVE:
-                // block all further touch inputs once the menu is open
-                if (mStack.isShowingBubbleMenu()) {
-                    return true;
-                }
                 trackMovement(event);
                 final float deltaX = rawX - mTouchDown.x;
                 final float deltaY = rawY - mTouchDown.y;
@@ -163,7 +146,6 @@
                 }
 
                 if (mMovedEnough) {
-                    mStack.removeCallbacks(mShowBubbleMenuRunnable);
                     if (isStack) {
                         mStack.onDragged(viewX, viewY);
                     } else if (isFlyout) {
@@ -194,12 +176,6 @@
                 break;
 
             case MotionEvent.ACTION_UP:
-                if (mStack.isShowingBubbleMenu()) {
-                    resetForNextGesture();
-                    return true;
-                } else {
-                    mStack.removeCallbacks(mShowBubbleMenuRunnable);
-                }
                 trackMovement(event);
                 mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
                 final float velX = mVelocityTracker.getXVelocity();
@@ -222,9 +198,14 @@
                                 if (isStack) {
                                     mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
                                 } else {
-                                    mController.removeBubble(
-                                            individualBubbleKey,
-                                            BubbleController.DISMISS_USER_GESTURE);
+                                    final Bubble bubble =
+                                            mBubbleData.getBubbleWithKey(individualBubbleKey);
+                                    // bubble can be null if the user is in the middle of
+                                    // dismissing the bubble, but the app also sent a cancel
+                                    if (bubble != null) {
+                                        mController.removeBubble(bubble.getEntry(),
+                                                BubbleController.DISMISS_USER_GESTURE);
+                                    }
                                 }
                             });
                 } else if (isFlyout) {
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..78e0e8b 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 = 3000L
 
 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..f029dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.ServiceConnection
+import android.graphics.drawable.Drawable
 import android.os.IBinder
 import android.service.controls.Control
 import android.service.controls.TokenProvider
@@ -29,19 +30,24 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.Space
-import android.widget.TextView
 
+import com.android.settingslib.widget.CandidateInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.R
+import com.android.systemui.util.concurrency.DelayableExecutor
 
 import dagger.Lazy
 
-import java.util.concurrent.Executor
+import java.text.Collator
+
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -104,18 +110,41 @@
     }
 }
 
+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,
+    @Background val bgExecutor: DelayableExecutor,
+    val controlsListingController: Lazy<ControlsListingController>
 ) : 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
+
+    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(candidates: List<CandidateInfo>) {
+            bgExecutor.execute {
+                val collator = Collator.getInstance(context.getResources()
+                        .getConfiguration().locale)
+                val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) {
+                    it.loadLabel()
+                }
+
+                val mList = candidates.toMutableList()
+                mList.sortWith(localeComparator)
+                loadInitialSetupViewIcons(mList.map { it.loadLabel() to it.loadIcon() })
+            }
+        }
+    }
+
     override fun show(parent: ViewGroup) {
         Log.d(TAG, "show()")
 
@@ -125,7 +154,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()
@@ -148,8 +177,26 @@
         val inflater = LayoutInflater.from(context)
         inflater.inflate(R.layout.controls_no_favorites, parent, true)
 
-        val textView = parent.requireViewById(R.id.controls_title) as TextView
-        textView.setOnClickListener(launchSelectorActivityListener(context))
+        val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup
+        viewGroup.setOnClickListener(launchSelectorActivityListener(context))
+
+        controlsListingController.get().addCallback(listingCallback)
+    }
+
+    private fun loadInitialSetupViewIcons(icons: List<Pair<CharSequence, Drawable>>) {
+        uiExecutor.execute {
+            val viewGroup = parent.requireViewById(R.id.controls_icon_row) as ViewGroup
+            viewGroup.removeAllViews()
+
+            val inflater = LayoutInflater.from(context)
+            icons.forEach {
+                val imageView = inflater.inflate(R.layout.controls_icon, viewGroup, false)
+                        as ImageView
+                imageView.setContentDescription(it.first)
+                imageView.setImageDrawable(it.second)
+                viewGroup.addView(imageView)
+            }
+        }
     }
 
     private fun launchSelectorActivityListener(context: Context): (View) -> Unit {
@@ -178,9 +225,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) {
@@ -200,31 +248,35 @@
         parent.removeAllViews()
         controlsById.clear()
         controlViewsById.clear()
+        controlsListingController.get().removeCallback(listingCallback)
     }
 
     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 {
-        val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup
-        parent.addView(row)
+    private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup {
+        val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup
+        listView.addView(row)
         return row
     }
 }
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/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 53a23b8..0ec739fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -30,6 +30,7 @@
 import android.view.LayoutInflater;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -191,6 +192,12 @@
         return new AlwaysOnDisplayPolicy(context);
     }
 
+    /***/
+    @Provides
+    public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
+        return new NotificationMessagingUtil(context);
+    }
+
     /** */
     @Provides
     public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7b54199..f068d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
+import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -68,7 +69,9 @@
             NotificationsModule.class,
             PeopleHubModule.class,
         },
-        subcomponents = {StatusBarComponent.class, NotificationRowComponent.class})
+        subcomponents = {StatusBarComponent.class,
+                NotificationRowComponent.class,
+                ExpandableNotificationRowComponent.class})
 public abstract class SystemUIModule {
 
     @Binds
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/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index c9c38d3..146f36b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -36,8 +36,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -49,8 +47,7 @@
 
 import dagger.Lazy;
 
-public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
-        PluginListener<GlobalActionsPanelPlugin> {
+public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
@@ -60,12 +57,9 @@
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private final BlurUtils mBlurUtils;
-    private GlobalActionsPanelPlugin mPlugin;
     private final CommandQueue mCommandQueue;
     private GlobalActionsDialog mGlobalActionsDialog;
     private boolean mDisabled;
-    private final PluginManager mPluginManager;
-    private final String mPluginPackageName;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
@@ -74,7 +68,6 @@
         mGlobalActionsDialogLazy = globalActionsDialogLazy;
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mPluginManager = Dependency.get(PluginManager.class);
         mCommandQueue = commandQueue;
         mBlurUtils = blurUtils;
         mCommandQueue.addCallback(this);
@@ -82,17 +75,11 @@
                 .newExtension(GlobalActionsPanelPlugin.class)
                 .withPlugin(GlobalActionsPanelPlugin.class)
                 .build();
-        mPluginPackageName = mContext.getString(
-                com.android.systemui.R.string.config_controlsPluginPackageName);
-        mPluginManager.addPluginListener(
-                GlobalActionsPanelPlugin.ACTION, this, GlobalActionsPanelPlugin.class, true);
     }
 
     @Override
     public void destroy() {
         mCommandQueue.removeCallback(this);
-        mPluginManager.removePluginListener(this);
-        if (mPlugin != null) mPlugin.onDestroy();
         if (mGlobalActionsDialog != null) {
             mGlobalActionsDialog.destroy();
             mGlobalActionsDialog = null;
@@ -105,7 +92,7 @@
         mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
         mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
-                mPlugin != null ? mPlugin : mPanelExtension.get());
+                mPanelExtension.get());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
     }
 
@@ -205,16 +192,4 @@
             mGlobalActionsDialog.dismissDialog();
         }
     }
-
-    @Override
-    public void onPluginConnected(GlobalActionsPanelPlugin plugin, Context pluginContext) {
-        if (pluginContext.getPackageName().equals(mPluginPackageName)) {
-            mPlugin = plugin;
-        }
-    }
-
-    @Override
-    public void onPluginDisconnected(GlobalActionsPanelPlugin plugin) {
-        mPlugin = null;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index 657a308..11e215d 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -153,11 +153,11 @@
         return true;
     }
 
-    private boolean checkExtensionCapability(String extName) {
+    boolean checkExtensionCapability(String extName) {
         return mExts.contains(extName);
     }
 
-    private int getWcgCapability() {
+    int getWcgCapability() {
         if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
             return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
         }
@@ -212,7 +212,7 @@
             if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
                 attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
             }
-            mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0);
+            mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */);
         } else {
             Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay()
                     + ", has valid surface=" + surfaceHolder.getSurface().isValid());
@@ -235,6 +235,10 @@
         return true;
     }
 
+    EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) {
+        return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset);
+    }
+
     /**
      * Destroy EglSurface.
      */
@@ -242,7 +246,7 @@
         if (hasEglSurface()) {
             eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
             eglDestroySurface(mEglDisplay, mEglSurface);
-            mEglSurface = null;
+            mEglSurface = EGL_NO_SURFACE;
         }
     }
 
@@ -296,7 +300,7 @@
     public void destroyEglContext() {
         if (hasEglContext()) {
             eglDestroyContext(mEglDisplay, mEglContext);
-            mEglContext = null;
+            mEglContext = EGL_NO_CONTEXT;
         }
     }
 
@@ -340,11 +344,16 @@
             destroyEglContext();
         }
         if (hasEglDisplay()) {
-            eglTerminate(mEglDisplay);
+            terminateEglDisplay();
         }
         mEglReady = false;
     }
 
+    void terminateEglDisplay() {
+        eglTerminate(mEglDisplay);
+        mEglDisplay = EGL_NO_DISPLAY;
+    }
+
     /**
      * Called to dump current state.
      * @param prefix prefix.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 14eec59..9da99c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -151,7 +151,7 @@
     private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
     private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
 
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final boolean DEBUG = true;
     private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
 
     private final static String TAG = "KeyguardViewMediator";
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/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 411980b..ae61622 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -83,7 +83,7 @@
         mTile = new Tile();
         updateDefaultTileAndIcon();
         mServiceManager = host.getTileServices().getTileWrapper(this);
-        if (mServiceManager.isBooleanTile()) {
+        if (mServiceManager.isToggleableTile()) {
             // Replace states with BooleanState
             resetStates();
         }
@@ -252,7 +252,7 @@
 
     @Override
     public State newTileState() {
-        if (mServiceManager != null && mServiceManager.isBooleanTile()) {
+        if (mServiceManager != null && mServiceManager.isToggleableTile()) {
             return new BooleanState();
         }
         return new State();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index ad79cad..17b0251 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -141,16 +141,16 @@
     /**
      * Determines whether the associated TileService is a Boolean Tile.
      *
-     * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
+     * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this
      *         tile
-     * @see TileService#META_DATA_BOOLEAN_TILE
+     * @see TileService#META_DATA_TOGGLEABLE_TILE
      */
-    public boolean isBooleanTile() {
+    public boolean isToggleableTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
                     PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
             return info.metaData != null
-                    && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
+                    && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 1902d65..cfa8fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -124,8 +124,8 @@
         return mStateManager.isActiveTile();
     }
 
-    public boolean isBooleanTile() {
-        return mStateManager.isBooleanTile();
+    public boolean isToggleableTile() {
+        return mStateManager.isToggleableTile();
     }
 
     public void setShowingDialog(boolean dialog) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index e1b61c6..e559694 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -109,10 +109,31 @@
 
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
 
+    /**
+     * Provides a new {@link TState} of the appropriate type to use between this tile and the
+     * corresponding view.
+     *
+     * @return new state to use by the tile.
+     */
     public abstract TState newTileState();
 
+    /**
+     * Handles clicks by the user.
+     *
+     * Calls to the controller should be made here to set the new state of the device.
+     */
     abstract protected void handleClick();
 
+    /**
+     * Update state of the tile based on device state
+     *
+     * Called whenever the state of the tile needs to be updated, either after user
+     * interaction or from callbacks from the controller. It populates {@code state} with the
+     * information to display to the user.
+     *
+     * @param state {@link TState} to populate with information to display
+     * @param arg additional arguments needed to populate {@code state}
+     */
     abstract protected void handleUpdateState(TState state, Object arg);
 
     /**
@@ -177,6 +198,12 @@
         return mHost;
     }
 
+    /**
+     * Return the {@link QSIconView} to be used by this tile's view.
+     *
+     * @param context view context for the view
+     * @return icon view for this tile
+     */
     public QSIconView createTileView(Context context) {
         return new QSIconViewImpl(context);
     }
@@ -298,11 +325,20 @@
         mCallbacks.clear();
     }
 
+    /**
+     * Handles secondary click on the tile.
+     *
+     * Defaults to {@link QSTileImpl#handleClick}
+     */
     protected void handleSecondaryClick() {
         // Default to normal click.
         handleClick();
     }
 
+    /**
+     * Handles long click on the tile by launching the {@link Intent} defined in
+     * {@link QSTileImpl#getLongClickIntent}
+     */
     protected void handleLongClick() {
         if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
             showDetail(true);
@@ -312,6 +348,11 @@
                 getLongClickIntent(), 0);
     }
 
+    /**
+     * Returns an intent to be launched when the tile is long pressed.
+     *
+     * @return the intent to launch
+     */
     public abstract Intent getLongClickIntent();
 
     protected void handleRefreshState(Object arg) {
@@ -427,6 +468,10 @@
         }
     }
 
+    /**
+     * Provides a default label for the tile.
+     * @return default label for the tile.
+     */
     public abstract CharSequence getTileLabel();
 
     public static int getColorForState(Context context, int state) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 001e094..42f8010 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.os.UserManager;
 import android.service.quicksettings.Tile;
+import android.util.Log;
 import android.widget.Switch;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -200,6 +201,14 @@
             mCallbackInfo.numConnectedDevices = numDevices;
             refreshState(mCallbackInfo);
         }
+
+        @Override
+        public void onHotspotAvailabilityChanged(boolean available) {
+            if (!available) {
+                Log.d(TAG, "Tile removed. Hotspot no longer available");
+                mHost.removeTile(getTileSpec());
+            }
+        }
     }
 
     /**
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/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 7fe229c..3fa1954 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -123,6 +123,16 @@
                         .append(" ");
             }
 
+            if (!notifEntry.mDismissInterceptors.isEmpty()) {
+                String[] interceptorsNames = new String[notifEntry.mDismissInterceptors.size()];
+                for (int i = 0; i < interceptorsNames.length; i++) {
+                    interceptorsNames[i] = notifEntry.mDismissInterceptors.get(i).getName();
+                }
+                rksb.append("dismissInterceptors=")
+                        .append(Arrays.toString(interceptorsNames))
+                        .append(" ");
+            }
+
             if (notifEntry.mExcludingFilter != null) {
                 rksb.append("filter=")
                         .append(notifEntry.mExcludingFilter)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 3b2fe94..38d8d97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.Assert;
 
@@ -116,6 +117,7 @@
     @Nullable private CollectionReadyForBuildListener mBuildListener;
     private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
     private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
+    private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
     private boolean mAttached = false;
     private boolean mAmDispatchingToOtherCode;
@@ -176,10 +178,21 @@
         extender.setCallback(this::onEndLifetimeExtension);
     }
 
+    /** @see NotifPipeline#addNotificationDismissInterceptor(NotifDismissInterceptor) */
+    void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) {
+        Assert.isMainThread();
+        checkForReentrantCall();
+        if (mDismissInterceptors.contains(interceptor)) {
+            throw new IllegalArgumentException("Interceptor " + interceptor + " already added.");
+        }
+        mDismissInterceptors.add(interceptor);
+        interceptor.setCallback(this::onEndDismissInterception);
+    }
+
     /**
      * Dismiss a notification on behalf of the user.
      */
-    void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+    public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
         Assert.isMainThread();
         requireNonNull(stats);
         checkForReentrantCall();
@@ -192,6 +205,12 @@
             return;
         }
 
+        updateDismissInterceptors(entry);
+        if (isDismissIntercepted(entry)) {
+            mLogger.logNotifDismissedIntercepted(entry.getKey());
+            return;
+        }
+
         // Optimistically mark the notification as dismissed -- we'll wait for the signal from
         // system server before removing it from our notification set.
         entry.setDismissState(DISMISSED);
@@ -236,7 +255,6 @@
         for (NotificationEntry canceledEntry : canceledEntries) {
             tryRemoveNotification(canceledEntry);
         }
-
         rebuildList();
     }
 
@@ -307,11 +325,11 @@
             // Update to an existing entry
             mLogger.logNotifUpdated(sbn.getKey());
 
+            // Notification is updated so it is essentially re-added and thus alive again, so we
+            // can reset its state.
             cancelLocalDismissal(entry);
-
-            // Notification is updated so it is essentially re-added and thus alive again. Don't
-            // need to keep its lifetime extended.
             cancelLifetimeExtension(entry);
+            cancelDismissInterception(entry);
             entry.mCancellationReason = REASON_NOT_CANCELED;
 
             entry.setSbn(sbn);
@@ -348,6 +366,7 @@
 
         if (!isLifetimeExtended(entry)) {
             mNotificationSet.remove(entry.getKey());
+            cancelDismissInterception(entry);
             dispatchOnEntryRemoved(entry, entry.mCancellationReason);
             dispatchOnEntryCleanUp(entry);
             return true;
@@ -436,6 +455,17 @@
         mAmDispatchingToOtherCode = false;
     }
 
+    private void updateDismissInterceptors(@NonNull NotificationEntry entry) {
+        entry.mDismissInterceptors.clear();
+        mAmDispatchingToOtherCode = true;
+        for (NotifDismissInterceptor interceptor : mDismissInterceptors) {
+            if (interceptor.shouldInterceptDismissal(entry)) {
+                entry.mDismissInterceptors.add(interceptor);
+            }
+        }
+        mAmDispatchingToOtherCode = false;
+    }
+
     private void cancelLocalDismissal(NotificationEntry entry) {
         if (isDismissedByUser(entry)) {
             entry.setDismissState(NOT_DISMISSED);
@@ -450,6 +480,42 @@
         }
     }
 
+    private void onEndDismissInterception(
+            NotifDismissInterceptor interceptor,
+            NotificationEntry entry,
+            @NonNull DismissedByUserStats stats) {
+        Assert.isMainThread();
+        if (!mAttached) {
+            return;
+        }
+        checkForReentrantCall();
+
+        if (!entry.mDismissInterceptors.remove(interceptor)) {
+            throw new IllegalStateException(
+                    String.format(
+                            "Cannot end dismiss interceptor for interceptor \"%s\" (%s)",
+                            interceptor.getName(),
+                            interceptor));
+        }
+
+        if (!isDismissIntercepted(entry)) {
+            dismissNotification(entry, stats);
+        }
+    }
+
+    private void cancelDismissInterception(NotificationEntry entry) {
+        mAmDispatchingToOtherCode = true;
+        for (NotifDismissInterceptor interceptor : entry.mDismissInterceptors) {
+            interceptor.cancelDismissInterception(entry);
+        }
+        mAmDispatchingToOtherCode = false;
+        entry.mDismissInterceptors.clear();
+    }
+
+    private boolean isDismissIntercepted(NotificationEntry entry) {
+        return entry.mDismissInterceptors.size() > 0;
+    }
+
     private void checkForReentrantCall() {
         if (mAmDispatchingToOtherCode) {
             throw new IllegalStateException("Reentrant call detected");
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..d4d2369 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,7 +23,9 @@
 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.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 
 import java.util.Collection;
@@ -66,7 +68,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,23 +91,28 @@
         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);
     }
 
     /**
      * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
-     * dismissed or retracted to be temporarily retained in the collection.
+     * dismissed or retracted by system server to be temporarily retained in the collection.
      */
     public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
         mNotifCollection.addNotificationLifetimeExtender(extender);
     }
 
     /**
+     * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been
+     * dismissed by the user to be retained (won't send a dismissal to system server).
+     */
+    public void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) {
+        mNotifCollection.addNotificationDismissInterceptor(interceptor);
+    }
+
+    /**
      * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
      * are called on each notification in the order that they were registered. If any filter
      * returns true, the notification is removed from the pipeline (and no other filters are
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..006d40d 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
@@ -66,8 +66,10 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
 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.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
@@ -104,6 +106,9 @@
     /** List of lifetime extenders that are extending the lifetime of this notification. */
     final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
 
+    /** List of dismiss interceptors that are intercepting the dismissal of this notification. */
+    final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
+
     /** If this notification was filtered out, then the filter that did the filtering. */
     @Nullable NotifFilter mExcludingFilter;
 
@@ -149,6 +154,7 @@
 
     private NotificationEntry parent; // our parent (if we're in a group)
     private ExpandableNotificationRow row; // the outer expanded view
+    private ExpandableNotificationRowController mRowController;
 
     private int mCachedContrastColor = COLOR_INVALID;
     private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -275,7 +281,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;
     }
 
@@ -415,6 +426,14 @@
         this.row = row;
     }
 
+    public ExpandableNotificationRowController getRowController() {
+        return mRowController;
+    }
+
+    public void setRowController(ExpandableNotificationRowController controller) {
+        mRowController = controller;
+    }
+
     @Nullable
     public List<NotificationEntry> getChildren() {
         if (row == null) {
@@ -595,12 +614,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/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
new file mode 100644
index 0000000..116c70c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -0,0 +1,164 @@
+/*
+ * 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 static android.service.notification.NotificationStats.DISMISSAL_OTHER;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Coordinates hiding, intercepting (the dismissal), and deletion of bubbled notifications.
+ *
+ * The typical "start state" for a bubbled notification is when a bubble-able notification is
+ * posted. It is visible as a bubble AND as a notification in the shade. From here, we can get
+ * into a few hidden-from-shade states described below:
+ *
+ * Start State -> Hidden from shade
+ * User expands the bubble so we hide its notification from the shade.
+ * OR
+ * User dismisses a group summary with a bubbled child. All bubbled children are now hidden from
+ * the shade. And the group summary's dismissal is intercepted + hidden from the shade (see below).
+ *
+ * Start State -> Dismissal intercepted + hidden from shade
+ * User dismisses the notification from the shade. We now hide the notification from the shade
+ * and intercept its dismissal (the removal signal is never sent to system server). We
+ * keep the notification alive in system server so that {@link BubbleController} can still
+ * respond to app-cancellations (ie: remove the bubble if the app cancels the notification).
+ *
+ */
+@Singleton
+public class BubbleCoordinator implements Coordinator {
+    private static final String TAG = "BubbleCoordinator";
+
+    private final BubbleController mBubbleController;
+    private final NotifCollection mNotifCollection;
+    private final Set<String> mInterceptedDismissalEntries = new HashSet<>();
+    private NotifPipeline mNotifPipeline;
+    private NotifDismissInterceptor.OnEndDismissInterception mOnEndDismissInterception;
+
+    @Inject
+    public BubbleCoordinator(
+            BubbleController bubbleController,
+            NotifCollection notifCollection) {
+        mBubbleController = bubbleController;
+        mNotifCollection = notifCollection;
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+        mNotifPipeline = pipeline;
+        mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
+        mNotifPipeline.addPreRenderFilter(mNotifFilter);
+        mBubbleController.addNotifCallback(mNotifCallback);
+    }
+
+    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return mBubbleController.isBubbleNotificationSuppressedFromShade(entry);
+        }
+    };
+
+    private final NotifDismissInterceptor mDismissInterceptor = new NotifDismissInterceptor() {
+        @Override
+        public String getName() {
+            return TAG;
+        }
+
+        @Override
+        public void setCallback(OnEndDismissInterception callback) {
+            mOnEndDismissInterception = callback;
+        }
+
+        @Override
+        public boolean shouldInterceptDismissal(NotificationEntry entry) {
+            // TODO: b/149041810 add support for intercepting app-cancelled bubble notifications
+            // for experimental bubbles
+            if (mBubbleController.handleDismissalInterception(entry)) {
+                mInterceptedDismissalEntries.add(entry.getKey());
+                return true;
+            } else {
+                mInterceptedDismissalEntries.remove(entry.getKey());
+                return false;
+            }
+        }
+
+        @Override
+        public void cancelDismissInterception(NotificationEntry entry) {
+            mInterceptedDismissalEntries.remove(entry.getKey());
+        }
+    };
+
+    private final BubbleController.NotifCallback mNotifCallback =
+            new BubbleController.NotifCallback() {
+        @Override
+        public void removeNotification(NotificationEntry entry, int reason) {
+            if (isInterceptingDismissal(entry)) {
+                mInterceptedDismissalEntries.remove(entry.getKey());
+                mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
+                        createDismissedByUserStats(entry));
+            } else if (mNotifPipeline.getActiveNotifs().contains(entry)) {
+                // Bubbles are hiding the notifications from the shade, but the bubble was
+                // deleted; therefore, the notification should be cancelled as if it were a user
+                // dismissal (this won't re-enter handleInterceptDimissal because Bubbles
+                // will have already marked it as no longer a bubble)
+                mNotifCollection.dismissNotification(entry, createDismissedByUserStats(entry));
+            }
+        }
+
+        @Override
+        public void invalidateNotifications(String reason) {
+            mNotifFilter.invalidateList();
+        }
+
+        @Override
+        public void maybeCancelSummary(NotificationEntry entry) {
+            // no-op
+        }
+    };
+
+    private boolean isInterceptingDismissal(NotificationEntry entry) {
+        return mInterceptedDismissalEntries.contains(entry.getKey());
+    }
+
+    private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
+        return new DismissedByUserStats(
+                DISMISSAL_OTHER,
+                DISMISS_SENTIMENT_UNKNOWN,
+                NotificationVisibility.obtain(entry.getKey(),
+                        entry.getRanking().getRank(),
+                        mNotifPipeline.getActiveNotifs().size(),
+                        true, // was visible as a bubble
+                        NotificationLogger.getNotificationLocation(entry))
+        );
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 0a1e09f..7a9547c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -53,6 +53,7 @@
             RankingCoordinator rankingCoordinator,
             ForegroundCoordinator foregroundCoordinator,
             DeviceProvisionedCoordinator deviceProvisionedCoordinator,
+            BubbleCoordinator bubbleCoordinator,
             PreparationCoordinator preparationCoordinator) {
         dumpController.registerDumpable(TAG, this);
 
@@ -61,6 +62,7 @@
         mCoordinators.add(rankingCoordinator);
         mCoordinators.add(foregroundCoordinator);
         mCoordinators.add(deviceProvisionedCoordinator);
+        mCoordinators.add(bubbleCoordinator);
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
             mCoordinators.add(preparationCoordinator);
         }
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..e8a62e4 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
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.collection.inflation;
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.annotation.Nullable;
@@ -40,16 +39,19 @@
 import com.android.systemui.statusbar.notification.NotificationClicker;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 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.ExpandableNotificationRowController;
+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.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.Objects;
 
@@ -64,36 +66,32 @@
 
     private static final String TAG = "NotificationViewManager";
 
-    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;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
-    private final boolean mAllowLongPress;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private final StatusBarStateController mStatusBarStateController;
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
-    private HeadsUpManager mHeadsUpManager;
     private NotificationRowContentBinder.InflationCallback mInflationCallback;
-    private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
     private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
-    private final NotificationLogger mNotificationLogger;
+    private final ExpandableNotificationRowComponent.Builder
+            mExpandableNotificationRowComponentBuilder;
 
     @Inject
     public NotificationRowBinderImpl(
             Context context,
+            NotificationMessagingUtil notificationMessagingUtil,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationRowContentBinder rowContentBinder,
+            NotifBindPipeline notifBindPipeline,
+            RowContentBindStage rowContentBindStage,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
@@ -101,20 +99,16 @@
             NotificationGutsManager notificationGutsManager,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
-            NotificationLogger logger) {
+            ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
         mContext = context;
-        mRowContentBinder = rowContentBinder;
-        mMessagingUtil = new NotificationMessagingUtil(context);
+        mNotifBindPipeline = notifBindPipeline;
+        mRowContentBindStage = rowContentBindStage;
+        mMessagingUtil = notificationMessagingUtil;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
-        mAllowLongPress = allowLongPress;
-        mKeyguardBypassController = keyguardBypassController;
-        mStatusBarStateController = statusBarStateController;
-        mGroupManager = notificationGroupManager;
-        mGutsManager = notificationGutsManager;
         mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
-        mNotificationLogger = logger;
+        mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
     }
 
     /**
@@ -122,13 +116,10 @@
      */
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
-            HeadsUpManager headsUpManager,
             BindRowCallback bindRowCallback) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mHeadsUpManager = headsUpManager;
         mBindRowCallback = bindRowCallback;
-        mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
     public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
@@ -143,9 +134,7 @@
      * Inflates the views for the given entry (possibly asynchronously).
      */
     @Override
-    public void inflateViews(
-            NotificationEntry entry,
-            Runnable onDismissRunnable)
+    public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable)
             throws InflationException {
         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
         PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
@@ -156,64 +145,39 @@
             entry.updateIcons(mContext, sbn);
             entry.reset();
             updateNotification(entry, pmUser, sbn, entry.getRow());
-            entry.getRow().setOnDismissRunnable(onDismissRunnable);
+            entry.getRowController().setOnDismissRunnable(onDismissRunnable);
         } else {
             entry.createIcons(mContext, sbn);
             mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
                     row -> {
-                        bindRow(entry, pmUser, sbn, row, onDismissRunnable);
+                        // Setup the controller for the view.
+                        ExpandableNotificationRowComponent component =
+                                mExpandableNotificationRowComponentBuilder
+                                        .expandableNotificationRow(row)
+                                        .notificationEntry(entry)
+                                        .onDismissRunnable(onDismissRunnable)
+                                        .inflationCallback(mInflationCallback)
+                                        .rowContentBindStage(mRowContentBindStage)
+                                        .onExpandClickListener(mPresenter)
+                                        .build();
+                        ExpandableNotificationRowController rowController =
+                                component.getExpandableNotificationRowController();
+                        rowController.init();
+                        entry.setRowController(rowController);
+                        bindRow(entry, pmUser, sbn, row);
                         updateNotification(entry, pmUser, sbn, row);
                     });
         }
     }
 
+    //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) {
-        // Get the app name.
-        // Note that Notification.Builder#bindHeaderAppName has similar logic
-        // but since this field is used in the guts, it must be accurate.
-        // Therefore we will only show the application label, or, failing that, the
-        // package name. No substitutions.
-        final String pkg = sbn.getPackageName();
-        String appname = pkg;
-        try {
-            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            if (info != null) {
-                appname = String.valueOf(pmUser.getApplicationLabel(info));
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // Do nothing
-        }
-
-        row.initialize(
-                appname,
-                sbn.getKey(),
-                mExpansionLogger,
-                mKeyguardBypassController,
-                mGroupManager,
-                mHeadsUpManager,
-                mRowContentBinder,
-                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);
-        }
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
         mListContainer.bindRow(row);
         mNotificationRemoteInputManager.bindRow(row);
-
-        row.setOnDismissRunnable(onDismissRunnable);
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-
+        entry.setRow(row);
+        row.setEntry(entry);
+        mNotifBindPipeline.manageRow(entry, row);
         mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
     }
 
@@ -247,13 +211,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,31 +230,36 @@
         // 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);
     }
 
-    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
-    }
-
     /** Callback for when a row is bound to an entry. */
     public interface BindRowCallback {
         /**
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/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 14e1503..dc7a50d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -69,6 +69,14 @@
         })
     }
 
+    fun logNotifDismissedIntercepted(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "DISMISS INTERCEPTED $str1"
+        })
+    }
+
     fun logRankingMissing(key: String, rankingMap: RankingMap) {
         buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
         buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java
new file mode 100644
index 0000000..3354ad1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java
@@ -0,0 +1,68 @@
+/*
+ * 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.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A way for coordinators to temporarily intercept a user-dismissed notification before a message
+ * is sent to system server to officially remove this notification.
+ * See {@link NotifCollection#addNotificationDismissInterceptor(NotifDismissInterceptor)}.
+ */
+public interface NotifDismissInterceptor {
+    /** Name to associate with this interceptor (for the purposes of debugging) */
+    String getName();
+
+    /**
+     * Called on the interceptor immediately after it has been registered. The interceptor should
+     * hang on to this callback and execute it whenever it no longer needs to intercept the
+     * dismissal of the notification.
+     */
+    void setCallback(OnEndDismissInterception callback);
+
+    /**
+     * Called by the NotifCollection whenever a notification has been dismissed (by the user).
+     * If the interceptor returns true, it is considered to be intercepting the notification.
+     * Intercepted notifications will not be sent to system server for removal until it is no
+     * longer being intercepted. However, the notification can still be cancelled by the app.
+     * This method is called on all interceptors even if earlier ones return true.
+     */
+    boolean shouldInterceptDismissal(NotificationEntry entry);
+
+
+    /**
+     * Called by the NotifCollection to inform a DismissInterceptor that its interception of a notif
+     * is no longer valid (usually because the notif has been removed by means other than the
+     * user dismissing the notification from the shade, or the notification has been updated). The
+     * interceptor should clean up any references it has to the notif in question.
+     */
+    void cancelDismissInterception(NotificationEntry entry);
+
+    /**
+     * Callback for notifying the NotifCollection that it no longer is intercepting the dismissal.
+     * If the end of this dismiss interception triggers a dismiss (ie: no other
+     * NotifDismissInterceptors are intercepting the entry), NotifCollection will use stats
+     * in the message sent to system server for the notification's dismissal.
+     */
+    interface OnEndDismissInterception {
+        void onEndDismissInterception(
+                NotifDismissInterceptor interceptor,
+                NotificationEntry entry,
+                DismissedByUserStats stats);
+    }
+}
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..3e0bcbb 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,
@@ -88,7 +90,6 @@
         notificationRowBinder.setUpWithPresenter(
                 presenter,
                 listContainer,
-                headsUpManager,
                 bindRowCallback)
 
         if (featureFlags.isNewNotifPipelineEnabled) {
@@ -98,6 +99,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/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 50a2037..3eac229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -33,16 +33,14 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
-import com.android.systemui.Dependency;
+import com.android.systemui.Gefingerpoken;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.DoubleTapHelper;
 
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
@@ -94,14 +92,12 @@
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
             = new PathInterpolator(0, 0, 0.5f, 1);
     private int mTintedRippleColor;
-    protected int mNormalRippleColor;
-    private final AccessibilityManager mAccessibilityManager;
-    private final DoubleTapHelper mDoubleTapHelper;
+    private int mNormalRippleColor;
+    private Gefingerpoken mTouchHandler;
 
     private boolean mDimmed;
 
-    protected int mBgTint = NO_COLOR;
-    private float mBgAlpha = 1f;
+    int mBgTint = NO_COLOR;
 
     /**
      * Flag to indicate that the notification has been touched once and the second touch will
@@ -116,7 +112,7 @@
     private Interpolator mCurrentAppearInterpolator;
     private Interpolator mCurrentAlphaInterpolator;
 
-    protected NotificationBackgroundView mBackgroundNormal;
+    NotificationBackgroundView mBackgroundNormal;
     private NotificationBackgroundView mBackgroundDimmed;
     private ObjectAnimator mBackgroundAnimator;
     private RectF mAppearAnimationRect = new RectF();
@@ -130,7 +126,6 @@
     private boolean mLastInSection;
     private boolean mFirstInSection;
     private boolean mIsBelowSpeedBump;
-    private final FalsingManager mFalsingManager;
 
     private float mNormalBackgroundVisibilityAmount;
     private float mDimmedBackgroundFadeInAmount = -1;
@@ -154,38 +149,25 @@
      */
     private boolean mNeedsDimming;
     private int mDimmedAlpha;
-    private boolean mBlockNextTouch;
     private boolean mIsHeadsUpAnimation;
     private int mHeadsUpAddStartLocation;
     private float mHeadsUpLocation;
     private boolean mIsAppearing;
     private boolean mDismissed;
     private boolean mRefocusOnDismiss;
+    private OnDimmedListener mOnDimmedListener;
+    private AccessibilityManager mAccessibilityManager;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
-        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         setClipChildren(false);
         setClipToPadding(false);
         updateColors();
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
-        mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
-            if (active) {
-                makeActive();
-            } else {
-                makeInactive(true /* animate */);
-            }
-        }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
         initDimens();
     }
 
-    public FalsingManager getFalsingManager() {
-        return mFalsingManager;
-    }
-
     private void updateColors() {
         mNormalColor = mContext.getColor(R.color.notification_material_background_color);
         mTintedRippleColor = mContext.getColor(
@@ -236,32 +218,15 @@
         mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
     }
 
-    private final Runnable mTapTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            makeInactive(true /* animate */);
-        }
-    };
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
-                && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
-            if (!mActivated) {
-                return true;
-            } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
-                mBlockNextTouch = true;
-                makeInactive(true /* animate */);
-                return true;
-            }
+        if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) {
+            return true;
         }
         return super.onInterceptTouchEvent(ev);
     }
 
-    private boolean isTouchExplorationEnabled() {
-        return mAccessibilityManager.isTouchExplorationEnabled();
-    }
-
     protected boolean disallowSingleClick(MotionEvent ev) {
         return false;
     }
@@ -270,25 +235,6 @@
         return false;
     }
 
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean result;
-        if (mBlockNextTouch) {
-            mBlockNextTouch = false;
-            return false;
-        }
-        if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
-            boolean wasActivated = mActivated;
-            result = handleTouchEventDimmed(event);
-            if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
-                removeCallbacks(mTapTimeoutRunnable);
-            }
-        } else {
-            result = super.onTouchEvent(event);
-        }
-        return result;
-    }
-
     /**
      * @return whether this view is interactive and can be double tapped
      */
@@ -313,28 +259,11 @@
         }
     }
 
-    public void setRippleAllowed(boolean allowed) {
+    void setRippleAllowed(boolean allowed) {
         mBackgroundNormal.setPressedAllowed(allowed);
     }
 
-    private boolean handleTouchEventDimmed(MotionEvent event) {
-        if (mNeedsDimming && !mDimmed) {
-            // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple
-            super.onTouchEvent(event);
-        }
-        return mDoubleTapHelper.onTouchEvent(event, getActualHeight());
-    }
-
-    @Override
-    public boolean performClick() {
-        if (!mNeedsDimming || isTouchExplorationEnabled()) {
-            return super.performClick();
-        }
-        return false;
-    }
-
-    private void makeActive() {
-        mFalsingManager.onNotificationActive();
+    void makeActive() {
         startActivateAnimation(false /* reverse */);
         mActivated = true;
         if (mOnActivatedListener != null) {
@@ -388,19 +317,25 @@
         mBackgroundNormal.animate()
                 .alpha(reverse ? 0f : 1f)
                 .setInterpolator(alphaInterpolator)
-                .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        float animatedFraction = animation.getAnimatedFraction();
-                        if (reverse) {
-                            animatedFraction = 1.0f - animatedFraction;
-                        }
-                        setNormalBackgroundVisibilityAmount(animatedFraction);
+                .setUpdateListener(animation -> {
+                    float animatedFraction = animation.getAnimatedFraction();
+                    if (reverse) {
+                        animatedFraction = 1.0f - animatedFraction;
                     }
+                    setNormalBackgroundVisibilityAmount(animatedFraction);
                 })
                 .setDuration(ACTIVATE_ANIMATION_LENGTH);
     }
 
+    @Override
+    public boolean performClick() {
+        if (!mNeedsDimming || (mAccessibilityManager != null
+                && mAccessibilityManager.isTouchExplorationEnabled())) {
+            return super.performClick();
+        }
+        return false;
+    }
+
     /**
      * Cancels the hotspot and makes the notification inactive.
      */
@@ -418,11 +353,13 @@
         if (mOnActivatedListener != null) {
             mOnActivatedListener.onActivationReset(this);
         }
-        removeCallbacks(mTapTimeoutRunnable);
     }
 
     public void setDimmed(boolean dimmed, boolean fade) {
         mNeedsDimming = dimmed;
+        if (mOnDimmedListener != null) {
+            mOnDimmedListener.onSetDimmed(dimmed);
+        }
         dimmed &= isDimmable();
         if (mDimmed != dimmed) {
             mDimmed = dimmed;
@@ -439,13 +376,17 @@
         return true;
     }
 
+    public boolean isDimmed() {
+        return mDimmed;
+    }
+
     private void updateOutlineAlpha() {
         float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
         alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
         setOutlineAlpha(alpha);
     }
 
-    public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
+    private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
         mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
         updateOutlineAlpha();
     }
@@ -473,14 +414,14 @@
     /**
      * Sets the tint color of the background
      */
-    public void setTintColor(int color) {
+    protected void setTintColor(int color) {
         setTintColor(color, false);
     }
 
     /**
      * Sets the tint color of the background
      */
-    public void setTintColor(int color, boolean animated) {
+    void setTintColor(int color, boolean animated) {
         if (color != mBgTint) {
             mBgTint = color;
             updateBackgroundTint(animated);
@@ -562,13 +503,10 @@
             mStartTint = mCurrentBackgroundTint;
             mTargetTint = color;
             mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
-            mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
-                            animation.getAnimatedFraction());
-                    setBackgroundTintColor(newColor);
-                }
+            mBackgroundColorAnimator.addUpdateListener(animation -> {
+                int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
+                        animation.getAnimatedFraction());
+                setBackgroundTintColor(newColor);
             });
             mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
             mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
@@ -643,11 +581,11 @@
     }
 
     protected void updateBackgroundAlpha(float transformationAmount) {
-        mBgAlpha =  isChildInGroup() && mDimmed ? transformationAmount : 1f;
+        float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f;
         if (mDimmedBackgroundFadeInAmount != -1) {
-            mBgAlpha *= mDimmedBackgroundFadeInAmount;
+            bgAlpha *= mDimmedBackgroundFadeInAmount;
         }
-        mBackgroundDimmed.setAlpha(mBgAlpha);
+        mBackgroundDimmed.setAlpha(bgAlpha);
     }
 
     protected void resetBackgroundAlpha() {
@@ -671,7 +609,6 @@
             mBackgroundDimmed.setVisibility(View.INVISIBLE);
             mBackgroundNormal.setVisibility(View.VISIBLE);
             mBackgroundNormal.setAlpha(1f);
-            removeCallbacks(mTapTimeoutRunnable);
             // make in inactive to avoid it sticking around active
             makeInactive(false /* animate */);
         }
@@ -783,14 +720,11 @@
         mAppearAnimator.setInterpolator(Interpolators.LINEAR);
         mAppearAnimator.setDuration(
                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
-        mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mAppearAnimationFraction = (float) animation.getAnimatedValue();
-                updateAppearAnimationAlpha();
-                updateAppearRect();
-                invalidate();
-            }
+        mAppearAnimator.addUpdateListener(animation -> {
+            mAppearAnimationFraction = (float) animation.getAnimatedValue();
+            updateAppearAnimationAlpha();
+            updateAppearRect();
+            invalidate();
         });
         if (animationListener != null) {
             mAppearAnimator.addListener(animationListener);
@@ -921,7 +855,7 @@
                 getCurrentBackgroundRadiusBottom());
     }
 
-    protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+    private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
         mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
         mBackgroundNormal.setRoundness(topRadius, bottomRadius);
     }
@@ -963,7 +897,7 @@
         }
     }
 
-    protected int getRippleColor() {
+    private int getRippleColor() {
         if (mBgTint != 0) {
             return mTintedRippleColor;
         } else {
@@ -1010,10 +944,6 @@
         mOnActivatedListener = onActivatedListener;
     }
 
-    public boolean hasSameBgColor(ActivatableNotificationView otherView) {
-        return calculateBgColor() == otherView.calculateBgColor();
-    }
-
     @Override
     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
             int outlineTranslation) {
@@ -1071,8 +1001,24 @@
         return mRefocusOnDismiss || isAccessibilityFocused();
     }
 
+    void setTouchHandler(Gefingerpoken touchHandler) {
+        mTouchHandler = touchHandler;
+    }
+
+    void setOnDimmedListener(OnDimmedListener onDimmedListener) {
+        mOnDimmedListener = onDimmedListener;
+    }
+
+    public void setAccessibilityManager(AccessibilityManager accessibilityManager) {
+        mAccessibilityManager = accessibilityManager;
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
     }
+
+    interface OnDimmedListener {
+        void onSetDimmed(boolean dimmed);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 18993ff..8465658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.view.MotionEvent;
+import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.systemui.Gefingerpoken;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.DoubleTapHelper;
 
 import javax.inject.Inject;
 
@@ -27,21 +31,102 @@
  */
 public class ActivatableNotificationViewController {
     private final ActivatableNotificationView mView;
+    private final ExpandableOutlineViewController mExpandableOutlineViewController;
     private final AccessibilityManager mAccessibilityManager;
     private final FalsingManager mFalsingManager;
+    private DoubleTapHelper mDoubleTapHelper;
+    private boolean mNeedsDimming;
+
+    private TouchHandler mTouchHandler = new TouchHandler();
 
     @Inject
     public ActivatableNotificationViewController(ActivatableNotificationView view,
+            ExpandableOutlineViewController expandableOutlineViewController,
             AccessibilityManager accessibilityManager, FalsingManager falsingManager) {
         mView = view;
+        mExpandableOutlineViewController = expandableOutlineViewController;
         mAccessibilityManager = accessibilityManager;
         mFalsingManager = falsingManager;
+
+        mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() {
+            @Override
+            public void onActivated(ActivatableNotificationView view) {
+                mFalsingManager.onNotificationActive();
+            }
+
+            @Override
+            public void onActivationReset(ActivatableNotificationView view) {
+            }
+        });
     }
 
     /**
      * Initialize the controller, setting up handlers and other behavior.
      */
     public void init() {
+        mExpandableOutlineViewController.init();
+        mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> {
+            if (active) {
+                mView.makeActive();
+                mFalsingManager.onNotificationActive();
+            } else {
+                mView.makeInactive(true /* animate */);
+            }
+        }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+        mView.setOnTouchListener(mTouchHandler);
+        mView.setTouchHandler(mTouchHandler);
+        mView.setOnDimmedListener(dimmed -> {
+            mNeedsDimming = dimmed;
+        });
+        mView.setAccessibilityManager(mAccessibilityManager);
+    }
 
+    class TouchHandler implements Gefingerpoken, View.OnTouchListener {
+        private boolean mBlockNextTouch;
+
+        @Override
+        public boolean onTouch(View v, MotionEvent ev) {
+            boolean result;
+            if (mBlockNextTouch) {
+                mBlockNextTouch = false;
+                return true;
+            }
+            if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
+                    && mView.isInteractive()) {
+                if (mNeedsDimming && !mView.isDimmed()) {
+                    // We're actually dimmed, but our content isn't dimmable,
+                    // let's ensure we have a ripple
+                    return false;
+                }
+                result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight());
+            } else {
+                return false;
+            }
+            return result;
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+                    && mView.disallowSingleClick(ev)
+                    && !mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (!mView.isActivated()) {
+                    return true;
+                } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+                    mBlockNextTouch = true;
+                    mView.makeInactive(true /* animate */);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Use {@link #onTouch(View, MotionEvent) instead}.
+         */
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            return false;
+        }
     }
 }
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..55173182 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;
 
@@ -44,7 +42,6 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -74,11 +71,11 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -88,8 +85,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 +122,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 +136,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;
@@ -211,6 +198,7 @@
     private NotificationGuts mGuts;
     private NotificationEntry mEntry;
     private String mAppName;
+    private FalsingManager mFalsingManager;
 
     /**
      * Whether or not the notification is using the heads up view and should peek from the top.
@@ -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();
@@ -1130,20 +1087,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mEntry.setInitializationTime(SystemClock.elapsedRealtime());
-        Dependency.get(PluginManager.class).addPluginListener(this,
-                NotificationMenuRowPlugin.class, false /* Allow multiple */);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(PluginManager.class).removePluginListener(this);
-    }
-
-    @Override
     public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
         boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null;
         if (existed) {
@@ -1243,8 +1186,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
@@ -1480,7 +1425,7 @@
         return mIsBlockingHelperShowing && mNotificationTranslationFinished;
     }
 
-    public void setOnDismissRunnable(Runnable onDismissRunnable) {
+    void setOnDismissRunnable(Runnable onDismissRunnable) {
         mOnDismissRunnable = onDismissRunnable;
     }
 
@@ -1598,7 +1543,6 @@
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
-        mBindParams.isLowPriority = mIsLowPriority;
         if (mChildrenContainer != null) {
             mChildrenContainer.setIsLowPriority(isLowPriority);
         }
@@ -1608,36 +1552,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 */);
         }
     }
 
@@ -1650,7 +1583,6 @@
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
-        mMediaManager = Dependency.get(NotificationMediaManager.class);
         initDimens();
     }
 
@@ -1664,8 +1596,12 @@
             KeyguardBypassController bypassController,
             NotificationGroupManager groupManager,
             HeadsUpManager headsUpManager,
-            NotificationRowContentBinder rowContentBinder,
-            OnExpandClickListener onExpandClickListener) {
+            RowContentBindStage rowContentBindStage,
+            OnExpandClickListener onExpandClickListener,
+            NotificationMediaManager notificationMediaManager,
+            OnAppOpsClickListener onAppOpsClickListener,
+            FalsingManager falsingManager,
+            StatusBarStateController statusBarStateController) {
         mAppName = appName;
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.setAppName(mAppName);
@@ -1676,11 +1612,11 @@
         mGroupManager = groupManager;
         mPrivateLayout.setGroupManager(groupManager);
         mHeadsUpManager = headsUpManager;
-        mNotificationContentBinder = rowContentBinder;
+        mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
-    }
-
-    public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
+        mMediaManager = notificationMediaManager;
+        setAppOpsOnClickListener(onAppOpsClickListener);
+        mFalsingManager = falsingManager;
         mStatusbarStateController = statusBarStateController;
     }
 
@@ -1772,7 +1708,7 @@
         return mOnAppOpsClickListener;
     }
 
-    public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {
+    void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {
         mOnAppOpsClickListener = v -> {
             createMenu();
             NotificationMenuRowPlugin provider = getProvider();
@@ -2241,7 +2177,7 @@
      * @param allowChildExpansion whether a call to this method allows expanding children
      */
     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
-        getFalsingManager().setNotificationExpanded();
+        mFalsingManager.setNotificationExpanded();
         if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
                 && !mChildrenContainer.showingAsLowPriority()) {
             final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
new file mode 100644
index 0000000..39fab43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -0,0 +1,161 @@
+/*
+ * 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.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.dagger.AppName;
+import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Controller for {@link ExpandableNotificationRow}.
+ */
+@NotificationRowScope
+public class ExpandableNotificationRowController {
+    private final ExpandableNotificationRow mView;
+    private final ActivatableNotificationViewController mActivatableNotificationViewController;
+    private final NotificationMediaManager mMediaManager;
+    private final PluginManager mPluginManager;
+    private final SystemClock mClock;
+    private final String mAppName;
+    private final String mNotificationKey;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final NotificationGroupManager mNotificationGroupManager;
+    private final RowContentBindStage mRowContentBindStage;
+    private final NotificationLogger mNotificationLogger;
+    private final HeadsUpManager mHeadsUpManager;
+    private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
+    private final StatusBarStateController mStatusBarStateController;
+    private final NotificationRowContentBinder.InflationCallback mInflationCallback;
+
+    private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
+            this::logNotificationExpansion;
+    private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
+    private final NotificationGutsManager mNotificationGutsManager;
+    private Runnable mOnDismissRunnable;
+    private final FalsingManager mFalsingManager;
+    private final boolean mAllowLongPress;
+
+    @Inject
+    public ExpandableNotificationRowController(ExpandableNotificationRow view,
+            ActivatableNotificationViewController activatableNotificationViewController,
+            NotificationMediaManager mediaManager, PluginManager pluginManager,
+            SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
+            KeyguardBypassController keyguardBypassController,
+            NotificationGroupManager notificationGroupManager,
+            RowContentBindStage rowContentBindStage,
+            NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
+            ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
+            StatusBarStateController statusBarStateController,
+            NotificationRowContentBinder.InflationCallback inflationCallback,
+            NotificationGutsManager notificationGutsManager,
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+            @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) {
+        mView = view;
+        mActivatableNotificationViewController = activatableNotificationViewController;
+        mMediaManager = mediaManager;
+        mPluginManager = pluginManager;
+        mClock = clock;
+        mAppName = appName;
+        mNotificationKey = notificationKey;
+        mKeyguardBypassController = keyguardBypassController;
+        mNotificationGroupManager = notificationGroupManager;
+        mRowContentBindStage = rowContentBindStage;
+        mNotificationLogger = notificationLogger;
+        mHeadsUpManager = headsUpManager;
+        mOnExpandClickListener = onExpandClickListener;
+        mStatusBarStateController = statusBarStateController;
+        mInflationCallback = inflationCallback;
+        mNotificationGutsManager = notificationGutsManager;
+        mOnDismissRunnable = onDismissRunnable;
+        mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
+        mAllowLongPress = allowLongPress;
+        mFalsingManager = falsingManager;
+    }
+
+    /**
+     * Initialize the controller.
+     */
+    public void init() {
+        mActivatableNotificationViewController.init();
+        mView.initialize(
+                mAppName,
+                mNotificationKey,
+                mExpansionLogger,
+                mKeyguardBypassController,
+                mNotificationGroupManager,
+                mHeadsUpManager,
+                mRowContentBindStage,
+                mOnExpandClickListener,
+                mMediaManager,
+                mOnAppOpsClickListener,
+                mFalsingManager,
+                mStatusBarStateController
+        );
+        mView.setOnDismissRunnable(mOnDismissRunnable);
+        mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (mAllowLongPress) {
+            mView.setLongPressListener(mNotificationGutsManager::openGuts);
+        }
+        if (ENABLE_REMOTE_INPUT) {
+            mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+                mPluginManager.addPluginListener(mView,
+                        NotificationMenuRowPlugin.class, false /* Allow multiple */);
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                mPluginManager.removePluginListener(mView);
+            }
+        });
+    }
+
+    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+    }
+
+    /** */
+    public void setOnDismissRunnable(Runnable onDismissRunnable) {
+        mOnDismissRunnable = onDismissRunnable;
+        mView.setOnDismissRunnable(onDismissRunnable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java
new file mode 100644
index 0000000..75c9d1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Controller for {@link ExpandableOutlineView}.
+ */
+public class ExpandableOutlineViewController {
+    private final ExpandableOutlineView mView;
+    private final ExpandableViewController mExpandableViewController;
+
+    @Inject
+    public ExpandableOutlineViewController(ExpandableOutlineView view,
+            ExpandableViewController expandableViewController) {
+        mView = view;
+        mExpandableViewController = expandableViewController;
+    }
+
+    /**
+     * Initialize the controller.
+     */
+    public void init() {
+        mExpandableViewController.init();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java
new file mode 100644
index 0000000..e14ca8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.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.row;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link ExpandableView}.
+ */
+public class ExpandableViewController {
+    private final ExpandableView mView;
+
+    @Inject
+    public ExpandableViewController(ExpandableView view) {
+        mView = view;
+    }
+
+    /**
+     * Initialize the controller.
+     */
+    public void init() {
+    }
+}
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/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/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index c173b4d..6feffe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -26,7 +26,6 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 
 import javax.inject.Inject;
 
@@ -37,7 +36,6 @@
 
     private static final String TAG = "RowInflaterTask";
     private static final boolean TRACE_ORIGIN = true;
-    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
 
     private RowInflationFinishedListener mListener;
     private NotificationEntry mEntry;
@@ -45,10 +43,7 @@
     private Throwable mInflateOrigin;
 
     @Inject
-    public RowInflaterTask(
-            NotificationRowComponent.Builder notificationRowComponentBuilder) {
-        super();
-        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
+    public RowInflaterTask() {
     }
 
     /**
@@ -75,12 +70,6 @@
     public void onInflateFinished(View view, int resid, ViewGroup parent) {
         if (!mCancelled) {
             try {
-                // Setup the controller for the view.
-                NotificationRowComponent component = mNotificationRowComponentBuilder
-                        .activatableNotificationView((ActivatableNotificationView) view)
-                        .build();
-                component.getActivatableNotificationViewController().init();
-
                 mEntry.onInflationTaskFinished();
                 mListener.onInflationFinished((ExpandableNotificationRow) view);
             } catch (Throwable t) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java
new file mode 100644
index 0000000..a3dfa60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.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.row.dagger;
+
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineView;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Module for NotificationRowComponent.
+ */
+@Module
+public interface ActivatableNotificationViewModule {
+    /** ExpandableView is provided as an instance of ActivatableNotificationView. */
+    @Binds
+    ExpandableView bindExpandableView(ActivatableNotificationView view);
+    /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */
+    @Binds
+    ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view);
+}
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java
similarity index 60%
copy from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java
index 3ad903b..1dbca0c 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java
@@ -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.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package com.android.systemui.statusbar.notification.row.dagger;
 
-parcelable PhoneTimeZoneSuggestion;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface AppName {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java
new file mode 100644
index 0000000..4331142
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DismissRunnable {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
new file mode 100644
index 0000000..6d6d3e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.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.systemui.statusbar.notification.row.dagger;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.service.notification.StatusBarNotification;
+
+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.ExpandableNotificationRowController;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import dagger.Binds;
+import dagger.BindsInstance;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * Dagger Component for a {@link ExpandableNotificationRow}.
+ */
+@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class,
+        ActivatableNotificationViewModule.class})
+@NotificationRowScope
+public interface ExpandableNotificationRowComponent {
+
+    /**
+     * Builder for {@link NotificationRowComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it
+        // should be possible to pull one from the other, but they aren't connected at the time
+        // this component is constructed.
+        @BindsInstance
+        Builder expandableNotificationRow(ExpandableNotificationRow view);
+        @BindsInstance
+        Builder notificationEntry(NotificationEntry entry);
+        @BindsInstance
+        Builder onDismissRunnable(@DismissRunnable Runnable runnable);
+        @BindsInstance
+        Builder rowContentBindStage(RowContentBindStage rowContentBindStage);
+        @BindsInstance
+        Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback);
+        @BindsInstance
+        Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
+        ExpandableNotificationRowComponent build();
+    }
+
+    /**
+     * Creates a ExpandableNotificationRowController.
+     */
+    @NotificationRowScope
+    ExpandableNotificationRowController getExpandableNotificationRowController();
+
+    /**
+     * Dagger Module that extracts interesting properties from an ExpandableNotificationRow.
+     */
+    @Module
+    abstract class ExpandableNotificationRowModule {
+
+        /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */
+        @Binds
+        abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view);
+
+        @Provides
+        static StatusBarNotification provideStatusBarNotification(
+                NotificationEntry notificationEntry) {
+            return notificationEntry.getSbn();
+        }
+
+        @Provides
+        @NotificationKey
+        static String provideNotificationKey(StatusBarNotification statusBarNotification) {
+            return statusBarNotification.getKey();
+        }
+
+        @Provides
+        @AppName
+        static String provideAppName(Context context, StatusBarNotification statusBarNotification) {
+            // Get the app name.
+            // Note that Notification.Builder#bindHeaderAppName has similar logic
+            // but since this field is used in the guts, it must be accurate.
+            // Therefore we will only show the application label, or, failing that, the
+            // package name. No substitutions.
+            PackageManager pmUser = StatusBar.getPackageManagerForUser(
+                    context, statusBarNotification.getUser().getIdentifier());
+            final String pkg = statusBarNotification.getPackageName();
+            try {
+                final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                | PackageManager.MATCH_DISABLED_COMPONENTS);
+                if (info != null) {
+                    return String.valueOf(pmUser.getApplicationLabel(info));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Do nothing
+            }
+
+            return pkg;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java
new file mode 100644
index 0000000..b1fff38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationKey {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
index f16ea7a..1f535c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
@@ -16,24 +16,17 @@
 
 package com.android.systemui.statusbar.notification.row.dagger;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
-
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Dagger subcomponent for Notification related views.
  */
-@Subcomponent(modules = {})
-@NotificationRowComponent.NotificationRowScope
+@Subcomponent(modules = {ActivatableNotificationViewModule.class})
+@NotificationRowScope
 public interface NotificationRowComponent {
     /**
      * Builder for {@link NotificationRowComponent}.
@@ -46,14 +39,6 @@
     }
 
     /**
-     * Scope annotation for singleton items within the StatusBarComponent.
-     */
-    @Documented
-    @Retention(RUNTIME)
-    @Scope
-    @interface NotificationRowScope {}
-
-    /**
      * Creates a ActivatableNotificationViewController.
      */
     @NotificationRowScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java
new file mode 100644
index 0000000..4555b83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface NotificationRowScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index db692c8..44a3204 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -422,9 +422,7 @@
     }
 
     private void updateDisplaySize() {
-        mContext.getSystemService(DisplayManager.class)
-                .getDisplay(mDisplayId)
-                .getRealSize(mDisplaySize);
+        mContext.getDisplay().getRealSize(mDisplaySize);
         if (mEdgeBackPlugin != null) {
             mEdgeBackPlugin.setDisplaySize(mDisplaySize);
         }
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/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 11f7079..c68d994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2466,10 +2466,6 @@
             pw.println("  mHeadsUpManager: null");
         }
 
-        if (mBubbleController != null) {
-            mBubbleController.dump(fd, pw, args);
-        }
-
         if (mLightBarController != null) {
             mLightBarController.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 830b50e..8231f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -30,5 +30,6 @@
 
     interface Callback {
         void onHotspotChanged(boolean enabled, int numDevices);
+        default void onHotspotAvailabilityChanged(boolean available) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index df9c3f4..d090404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.TetheringManager;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
@@ -26,6 +27,8 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.io.FileDescriptor;
@@ -46,36 +49,63 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-    private final ConnectivityManager mConnectivityManager;
+    private final TetheringManager mTetheringManager;
     private final WifiManager mWifiManager;
     private final Handler mMainHandler;
     private final Context mContext;
 
     private int mHotspotState;
     private volatile int mNumConnectedDevices;
+    private volatile boolean mIsTetheringSupported;
+    private volatile boolean mHasTetherableWifiRegexs;
     private boolean mWaitingForTerminalState;
 
+    private TetheringManager.TetheringEventCallback mTetheringCallback =
+            new TetheringManager.TetheringEventCallback() {
+                @Override
+                public void onTetheringSupported(boolean supported) {
+                    super.onTetheringSupported(supported);
+                    if (mIsTetheringSupported != supported) {
+                        mIsTetheringSupported = supported;
+                        fireHotspotAvailabilityChanged();
+                    }
+                }
+
+                @Override
+                public void onTetherableInterfaceRegexpsChanged(
+                        TetheringManager.TetheringInterfaceRegexps reg) {
+                    super.onTetherableInterfaceRegexpsChanged(reg);
+                    final boolean newValue = reg.getTetherableWifiRegexs().size() != 0;
+                    if (mHasTetherableWifiRegexs != newValue) {
+                        mHasTetherableWifiRegexs = newValue;
+                        fireHotspotAvailabilityChanged();
+                    }
+                }
+            };
+
     /**
      * Controller used to retrieve information related to a hotspot.
      */
     @Inject
-    public HotspotControllerImpl(Context context, @Main Handler mainHandler) {
+    public HotspotControllerImpl(Context context, @Main Handler mainHandler,
+            @Background Handler backgroundHandler) {
         mContext = context;
-        mConnectivityManager =
-                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mTetheringManager = context.getSystemService(TetheringManager.class);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mMainHandler = mainHandler;
+        mTetheringManager.registerTetheringEventCallback(
+                new HandlerExecutor(backgroundHandler), mTetheringCallback);
     }
 
     @Override
     public boolean isHotspotSupported() {
-        return mConnectivityManager.isTetheringSupported()
-                && mConnectivityManager.getTetherableWifiRegexs().length != 0
+        return mIsTetheringSupported && mHasTetherableWifiRegexs
                 && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("HotspotController state:");
+        pw.print("  available="); pw.println(isHotspotSupported());
         pw.print("  mHotspotState="); pw.println(stateToString(mHotspotState));
         pw.print("  mNumConnectedDevices="); pw.println(mNumConnectedDevices);
         pw.print("  mWaitingForTerminalState="); pw.println(mWaitingForTerminalState);
@@ -152,17 +182,18 @@
         if (enabled) {
             mWaitingForTerminalState = true;
             if (DEBUG) Log.d(TAG, "Starting tethering");
-            mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false,
-                    new ConnectivityManager.OnStartTetheringCallback() {
+            mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI,
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    new TetheringManager.StartTetheringCallback() {
                         @Override
-                        public void onTetheringFailed() {
+                        public void onTetheringFailed(final int result) {
                             if (DEBUG) Log.d(TAG, "onTetheringFailed");
                             maybeResetSoftApState();
                             fireHotspotChangedCallback();
                         }
                     });
         } else {
-            mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+            mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
         }
     }
 
@@ -177,10 +208,25 @@
      * (as it can be blocked).
      */
     private void fireHotspotChangedCallback() {
+        List<Callback> list;
         synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices);
-            }
+            list = new ArrayList<>(mCallbacks);
+        }
+        for (Callback callback : list) {
+            callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices);
+        }
+    }
+
+    /**
+     * Sends a hotspot available changed callback.
+     */
+    private void fireHotspotAvailabilityChanged() {
+        List<Callback> list;
+        synchronized (mCallbacks) {
+            list = new ArrayList<>(mCallbacks);
+        }
+        for (Callback callback : list) {
+            callback.onHotspotAvailabilityChanged(isHotspotSupported());
         }
     }
 
@@ -206,7 +252,7 @@
         switch (mHotspotState) {
             case WifiManager.WIFI_AP_STATE_FAILED:
                 // TODO(b/110697252): must be called to reset soft ap state after failure
-                mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
                 // Fall through
             case WifiManager.WIFI_AP_STATE_ENABLED:
             case WifiManager.WIFI_AP_STATE_DISABLED:
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..70bcc214
--- /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/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cfd77be..f4157f2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -901,6 +901,23 @@
             verboseLogging = debug
         }
 
+        /**
+         * Estimates the end value of a fling that starts at the given value using the provided
+         * start velocity and fling configuration.
+         *
+         * This is only an estimate. Fling animations use a timing-based physics simulation that is
+         * non-deterministic, so this exact value may not be reached.
+         */
+        @JvmStatic
+        fun estimateFlingEndValue(
+            startValue: Float,
+            startVelocity: Float,
+            flingConfig: FlingConfig
+        ): Float {
+            val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+            return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance))
+        }
+
         @JvmStatic
         fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
             return when (property) {
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..c3b55e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -20,8 +20,7 @@
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -34,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -55,23 +55,26 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.DumpController;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
 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;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 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;
@@ -81,7 +84,6 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -93,6 +95,13 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
+/**
+ * Tests the NotificationEntryManager setup with BubbleController.
+ * The {@link NotifPipeline} setup with BubbleController is tested in
+ * {@link NewNotifPipelineBubbleControllerTest}.
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -152,9 +161,13 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
-    private RemoteInputUriController mRemoteInputUriController;
-    @Mock
     private NotificationRowComponent mNotificationRowComponent;
+    @Mock
+    private NotifPipeline mNotifPipeline;
+    @Mock
+    private FeatureFlags mFeatureFlagsOldPipeline;
+    @Mock
+    private DumpController mDumpController;
 
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private BubbleData mBubbleData;
@@ -216,6 +229,7 @@
                 mock(HeadsUpManager.class),
                 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
+        when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mBubbleController = new TestableBubbleController(mContext,
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
@@ -227,7 +241,9 @@
                 mLockscreenUserManager,
                 mNotificationGroupManager,
                 mNotificationEntryManager,
-                mRemoteInputUriController);
+                mNotifPipeline,
+                mFeatureFlagsOldPipeline,
+                mDumpController);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -265,7 +281,7 @@
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
 
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
@@ -286,12 +302,12 @@
 
         // Now remove the bubble
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
 
         // Since the notif is dismissed, once the bubble is removed, performRemoveNotification gets
         // called to really remove the notif
         verify(mNotificationEntryManager, times(1)).performRemoveNotification(
-                mRow.getEntry().getSbn(), UNDEFINED_DISMISS_REASON);
+                eq(mRow.getEntry().getSbn()), anyInt());
         assertFalse(mBubbleController.hasBubbles());
     }
 
@@ -471,7 +487,7 @@
                 mRow2.getEntry()));
 
         // Dismiss currently expanded
-        mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
+        mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
 
@@ -480,7 +496,7 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
         // Dismiss that one
-        mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
+        mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
 
         // Make sure state changes and collapse happens
@@ -608,7 +624,7 @@
     @Test
     public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
-        mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
         verify(mDeleteIntent, never()).send();
     }
 
@@ -616,7 +632,7 @@
     public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(1)).send();
     }
 
@@ -653,11 +669,22 @@
 
         // Cancels always remove so no need to intercept
         assertFalse(intercepted);
+    }
+
+    @Test
+    public void testRemoveBubble_entryListenerRemove() {
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Removes the notification
+        mEntryListener.onEntryRemoved(mRow.getEntry(), null, false);
         assertFalse(mBubbleController.hasBubbles());
     }
 
     @Test
-    public void removeBubble_fails_clearAll()  {
+    public void removeBubble_clearAllIntercepted()  {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
@@ -673,14 +700,10 @@
         // Should update show in shade state
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry()));
-
-        verify(mNotificationEntryManager, never()).performRemoveNotification(
-                any(), anyInt());
-        assertTrue(mBubbleController.hasBubbles());
     }
 
     @Test
-    public void removeBubble_fails_userDismissNotif() {
+    public void removeBubble_userDismissNotifIntercepted() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
@@ -696,10 +719,6 @@
         // Should update show in shade state
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry()));
-
-        verify(mNotificationEntryManager, never()).performRemoveNotification(
-                any(), anyInt());
-        assertTrue(mBubbleController.hasBubbles());
     }
 
     @Test
@@ -713,7 +732,7 @@
 
         // Dismiss the bubble
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mBubbleController.hasBubbles());
 
         // Dismiss the notification
@@ -771,6 +790,74 @@
                 mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
     }
 
+    @Test
+    public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
+        // GIVEN a group summary with a bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+        assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+
+        // WHEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // THEN the summary and bubbled child are suppressed from the shade
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupedBubble.getEntry()));
+        assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
+    }
+
+    @Test
+    public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
+        // GIVEN a group summary with a bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+        assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+
+        // GIVEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // WHEN the summary is cancelled by the app
+        mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, true);
+
+        // THEN the summary and its children are removed from bubble data
+        assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+        assertFalse(mBubbleData.isSummarySuppressed(
+                groupSummary.getEntry().getSbn().getGroupKey()));
+    }
+
+    @Test
+    public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
+            throws Exception {
+        // GIVEN a group summary with two (non-bubble) children and one bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+
+        // WHEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // THEN only the NON-bubble children are dismissed
+        List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren();
+        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
+                childrenRows.get(0).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED);
+        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
+                childrenRows.get(1).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED);
+        verify(mNotificationEntryManager, never()).performRemoveNotification(
+                eq(groupedBubble.getEntry().getSbn()), anyInt());
+
+        // THEN the bubble child is suppressed from the shade
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupedBubble.getEntry()));
+
+        // THEN the summary is removed from GroupManager
+        verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
+    }
+
     static class TestableBubbleController extends BubbleController {
         // Let's assume surfaces can be synchronized immediately.
         TestableBubbleController(Context context,
@@ -784,12 +871,14 @@
                 NotificationLockscreenUserManager lockscreenUserManager,
                 NotificationGroupManager groupManager,
                 NotificationEntryManager entryManager,
-                RemoteInputUriController remoteInputUriController) {
+                NotifPipeline notifPipeline,
+                FeatureFlags featureFlags,
+                DumpController dumpController) {
             super(context,
                     notificationShadeWindowController, statusBarStateController, shadeController,
                     data, Runnable::run, configurationController, interruptionStateProvider,
                     zenModeController, lockscreenUserManager, groupManager, entryManager,
-                    remoteInputUriController);
+                     notifPipeline, featureFlags, dumpController);
             setInflateSynchronously(true);
         }
     }
@@ -806,7 +895,7 @@
     }
 
     /**
-     * Sets the bubble metadata flags for this entry. These flags are normally set by
+     * Sets the bubble metadata flags for this entry. These ]flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
      * go through that path so we set them explicitly when testing.
      */
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/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
new file mode 100644
index 0000000..72405fc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -0,0 +1,842 @@
+/*
+ * 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.bubbles;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.face.FaceManager;
+import android.service.notification.ZenModeConfig;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.DumpController;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+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;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.InjectionInflationController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Tests the NotifPipeline setup with BubbleController.
+ * The NotificationEntryManager setup with BubbleController is tested in
+ * {@link BubbleControllerTest}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
+    @Mock
+    private NotificationEntryManager mNotificationEntryManager;
+    @Mock
+    private NotificationGroupManager mNotificationGroupManager;
+    @Mock
+    private BubbleController.NotifCallback mNotifCallback;
+    @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private IActivityManager mActivityManager;
+    @Mock
+    private DozeParameters mDozeParameters;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private ZenModeController mZenModeController;
+    @Mock
+    private ZenModeConfig mZenModeConfig;
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private KeyguardBypassController mKeyguardBypassController;
+
+    @Captor
+    private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
+
+    private TestableBubbleController mBubbleController;
+    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotifCollectionListener mEntryListener;
+
+    private NotificationTestHelper mNotificationTestHelper;
+    private ExpandableNotificationRow mRow;
+    private ExpandableNotificationRow mRow2;
+    private ExpandableNotificationRow mNonBubbleNotifRow;
+
+    @Mock
+    private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener;
+    @Mock
+    private BubbleController.BubbleExpandListener mBubbleExpandListener;
+    @Mock
+    private PendingIntent mDeleteIntent;
+    @Mock
+    private SysuiColorExtractor mColorExtractor;
+    @Mock
+    ColorExtractor.GradientColors mGradientColors;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private ShadeController mShadeController;
+    @Mock
+    private NotificationRowComponent mNotificationRowComponent;
+    @Mock
+    private NotifPipeline mNotifPipeline;
+    @Mock
+    private FeatureFlags mFeatureFlagsNewPipeline;
+    @Mock
+    private DumpController mDumpController;
+
+    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+    private BubbleData mBubbleData;
+
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTestableLooper = TestableLooper.get(this);
+
+        mContext.addMockSystemService(FaceManager.class, mFaceManager);
+        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
+
+        mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
+                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
+                new NotificationRowComponent.Builder() {
+                    @Override
+                    public NotificationRowComponent.Builder activatableNotificationView(
+                            ActivatableNotificationView view) {
+                        return this;
+                    }
+
+                    @Override
+                    public NotificationRowComponent build() {
+                        return mNotificationRowComponent;
+                    }
+                });
+
+        // Bubbles get added to status bar window view
+        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+                mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+                mConfigurationController, mKeyguardBypassController, mColorExtractor,
+                mSuperStatusBarViewFactory);
+        mNotificationShadeWindowController.attach();
+
+        // Need notifications for bubbles
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
+        mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
+        mNonBubbleNotifRow = mNotificationTestHelper.createRow();
+
+        mZenModeConfig.suppressedVisualEffects = 0;
+        when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
+
+        TestableNotificationInterruptionStateProvider interruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mock(NotificationFilter.class),
+                        mock(StatusBarStateController.class),
+                        mock(BatteryController.class));
+        interruptionStateProvider.setUpWithPresenter(
+                mock(NotificationPresenter.class),
+                mock(HeadsUpManager.class),
+                mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
+        mBubbleData = new BubbleData(mContext);
+        when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
+        mBubbleController = new TestableBubbleController(mContext,
+                mNotificationShadeWindowController,
+                mStatusBarStateController,
+                mShadeController,
+                mBubbleData,
+                mConfigurationController,
+                interruptionStateProvider,
+                mZenModeController,
+                mLockscreenUserManager,
+                mNotificationGroupManager,
+                mNotificationEntryManager,
+                mNotifPipeline,
+                mFeatureFlagsNewPipeline,
+                mDumpController);
+        mBubbleController.addNotifCallback(mNotifCallback);
+        mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
+        mBubbleController.setExpandListener(mBubbleExpandListener);
+
+        // Get a reference to the BubbleController's entry listener
+        verify(mNotifPipeline, atLeastOnce())
+                .addCollectionListener(mNotifListenerCaptor.capture());
+        mEntryListener = mNotifListenerCaptor.getValue();
+    }
+
+    @Test
+    public void testAddBubble() {
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertTrue(mBubbleController.hasBubbles());
+
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
+    }
+
+    @Test
+    public void testHasBubbles() {
+        assertFalse(mBubbleController.hasBubbles());
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertTrue(mBubbleController.hasBubbles());
+    }
+
+    @Test
+    public void testRemoveBubble() {
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+        assertTrue(mBubbleController.hasBubbles());
+        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
+
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+        assertFalse(mNotificationShadeWindowController.getBubblesShowing());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+        verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
+    }
+
+    @Test
+    public void testRemoveBubble_withDismissedNotif() {
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
+
+        // Make it look like dismissed notif
+        mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
+
+        // Now remove the bubble
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+
+        // Since the notif is dismissed, once the bubble is removed, removeNotification gets
+        // called to really remove the notif
+        verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), anyInt());
+        assertFalse(mBubbleController.hasBubbles());
+    }
+
+    @Test
+    public void testDismissStack() {
+        mBubbleController.updateBubble(mRow.getEntry());
+        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+        mBubbleController.updateBubble(mRow2.getEntry());
+        verify(mNotifCallback,  times(2)).invalidateNotifications(anyString());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
+        assertTrue(mBubbleController.hasBubbles());
+
+        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        assertFalse(mNotificationShadeWindowController.getBubblesShowing());
+        verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+        assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
+    }
+
+    @Test
+    public void testExpandCollapseStack() {
+        assertFalse(mBubbleController.isStackExpanded());
+
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+        assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
+
+        // Make sure the notif is suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
+
+        // Collapse
+        mBubbleController.collapseStack();
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+        assertFalse(mBubbleController.isStackExpanded());
+        assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
+    }
+
+    @Test
+    public void testCollapseAfterChangingExpandedBubble() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mEntryListener.onEntryAdded(mRow2.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow2.getEntry()));
+
+        // Expand
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
+
+        // Last added is the one that is expanded
+        assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
+
+        // Switch which bubble is expanded
+        mBubbleController.selectBubble(mRow.getEntry().getKey());
+        mBubbleData.setExpanded(true);
+        assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        // collapse for previous bubble
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
+        // expand for selected bubble
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Collapse
+        mBubbleController.collapseStack();
+        assertFalse(mBubbleController.isStackExpanded());
+    }
+
+    @Test
+    public void testExpansionRemovesShowInShadeAndDot() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
+
+        mTestableLooper.processAllMessages();
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+
+        // Expand
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+    }
+
+    @Test
+    public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        mTestableLooper.processAllMessages();
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+
+        // Expand
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+
+        // Send update
+        mEntryListener.onEntryUpdated(mRow.getEntry());
+
+        // Nothing should have changed
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+    }
+
+    @Test
+    public void testRemoveLastExpandedCollapses() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mEntryListener.onEntryAdded(mRow2.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
+
+        // Expand
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleController.expandStack();
+
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
+
+        // Last added is the one that is expanded
+        assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow2.getEntry()));
+
+        // Dismiss currently expanded
+        mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(),
+                BubbleController.DISMISS_USER_GESTURE);
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
+
+        // Make sure first bubble is selected
+        assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Dismiss that one
+        mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(),
+                BubbleController.DISMISS_USER_GESTURE);
+
+        // Make sure state changes and collapse happens
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
+        assertFalse(mBubbleController.hasBubbles());
+    }
+
+    @Test
+    public void testAutoExpand_fails_noFlag() {
+        assertFalse(mBubbleController.isStackExpanded());
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
+
+        // Add the auto expand bubble
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // Expansion shouldn't change
+        verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
+                mRow.getEntry().getKey());
+        assertFalse(mBubbleController.isStackExpanded());
+
+        // # of bubbles should change
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+    }
+
+    @Test
+    public void testAutoExpand_succeeds_withFlag() {
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
+
+        // Add the auto expand bubble
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // Expansion should change
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
+                mRow.getEntry().getKey());
+        assertTrue(mBubbleController.isStackExpanded());
+
+        // # of bubbles should change
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+    }
+
+    @Test
+    public void testSuppressNotif_onInitialNotif() {
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
+
+        // Add the suppress notif bubble
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // Notif should be suppressed because we were foreground
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Dot + flyout is hidden because notif is suppressed
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
+
+        // # of bubbles should change
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+    }
+
+    @Test
+    public void testSuppressNotif_onUpdateNotif() {
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // Should not be suppressed
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Should show dot
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+
+        // Update to suppress notif
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // Notif should be suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+        // Dot + flyout is hidden because notif is suppressed
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
+
+        // # of bubbles should change
+        verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+    }
+
+    @Test
+    public void testMarkNewNotificationAsShowInShade() {
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        mTestableLooper.processAllMessages();
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+    }
+
+    @Test
+    public void testAddNotif_notBubble() {
+        mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
+
+        verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
+        assertThat(mBubbleController.hasBubbles()).isFalse();
+    }
+
+    @Test
+    public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
+        verify(mDeleteIntent, never()).send();
+    }
+
+    @Test
+    public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.removeBubble(
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+        verify(mDeleteIntent, times(1)).send();
+    }
+
+    @Test
+    public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
+        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        verify(mDeleteIntent, times(2)).send();
+    }
+
+    @Test
+    public void testRemoveBubble_noLongerBubbleAfterUpdate()
+            throws PendingIntent.CanceledException {
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertTrue(mBubbleController.hasBubbles());
+
+        mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
+        mEntryListener.onEntryUpdated(mRow.getEntry());
+
+        assertFalse(mBubbleController.hasBubbles());
+        verify(mDeleteIntent, never()).send();
+    }
+
+    @Test
+    public void testRemoveBubble_entryListenerRemove() {
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Removes the notification
+        mEntryListener.onEntryRemoved(mRow.getEntry(), 0);
+        assertFalse(mBubbleController.hasBubbles());
+    }
+
+    @Test
+    public void removeBubble_intercepted()  {
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
+
+        // Intercept!
+        assertTrue(intercepted);
+        // Should update show in shade state
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
+    }
+
+    @Test
+    public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        // Dismiss the bubble
+        mBubbleController.removeBubble(
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+        assertFalse(mBubbleController.hasBubbles());
+
+        // Dismiss the notification
+        boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
+
+        // It's no longer a bubble so we shouldn't intercept
+        assertFalse(intercepted);
+    }
+
+    @Test
+    public void testNotifyShadeSuppressionChange_notificationDismiss() {
+        BubbleController.NotificationSuppressionChangedListener listener =
+                mock(BubbleController.NotificationSuppressionChangedListener.class);
+        mBubbleData.setSuppressionChangedListener(listener);
+
+        mEntryListener.onEntryAdded(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        mBubbleController.handleDismissalInterception(mRow.getEntry());
+
+        // Should update show in shade state
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        // Should notify delegate that shade state changed
+        verify(listener).onBubbleNotificationSuppressionChange(
+                mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+    }
+
+    @Test
+    public void testNotifyShadeSuppressionChange_bubbleExpanded() {
+        BubbleController.NotificationSuppressionChangedListener listener =
+                mock(BubbleController.NotificationSuppressionChangedListener.class);
+        mBubbleData.setSuppressionChangedListener(listener);
+
+        mEntryListener.onEntryAdded(mRow.getEntry());
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        mBubbleData.setExpanded(true);
+
+        // Once a bubble is expanded the notif is suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow.getEntry()));
+
+        // Should notify delegate that shade state changed
+        verify(listener).onBubbleNotificationSuppressionChange(
+                mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
+    }
+
+    @Test
+    public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
+        // GIVEN a group summary with a bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+        assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+
+        // WHEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // THEN the summary and bubbled child are suppressed from the shade
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupedBubble.getEntry()));
+        assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
+    }
+
+    @Test
+    public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
+        // GIVEN a group summary with a bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+        assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+
+        // GIVEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // WHEN the summary is cancelled by the app
+        mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
+
+        // THEN the summary and its children are removed from bubble data
+        assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+        assertFalse(mBubbleData.isSummarySuppressed(
+                groupSummary.getEntry().getSbn().getGroupKey()));
+    }
+
+    @Test
+    public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
+            throws Exception {
+        // GIVEN a group summary with two (non-bubble) children and one bubble child
+        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
+        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
+        groupSummary.addChildNotification(groupedBubble);
+
+        // WHEN the summary is dismissed
+        mBubbleController.handleDismissalInterception(groupSummary.getEntry());
+
+        // THEN only the NON-bubble children are dismissed
+        List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren();
+        verify(mNotifCallback, times(1)).removeNotification(
+                childrenRows.get(0).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
+        verify(mNotifCallback, times(1)).removeNotification(
+                childrenRows.get(1).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
+        verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), anyInt());
+
+        // THEN the bubble child still exists as a bubble and is suppressed from the shade
+        assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupedBubble.getEntry()));
+
+        // THEN the summary is also suppressed from the shade
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupSummary.getEntry()));
+    }
+
+    static class TestableBubbleController extends BubbleController {
+        // Let's assume surfaces can be synchronized immediately.
+        TestableBubbleController(Context context,
+                NotificationShadeWindowController notificationShadeWindowController,
+                StatusBarStateController statusBarStateController,
+                ShadeController shadeController,
+                BubbleData data,
+                ConfigurationController configurationController,
+                NotificationInterruptionStateProvider interruptionStateProvider,
+                ZenModeController zenModeController,
+                NotificationLockscreenUserManager lockscreenUserManager,
+                NotificationGroupManager groupManager,
+                NotificationEntryManager entryManager,
+                NotifPipeline notifPipeline,
+                FeatureFlags featureFlags,
+                DumpController dumpController) {
+            super(context,
+                    notificationShadeWindowController, statusBarStateController, shadeController,
+                    data, Runnable::run, configurationController, interruptionStateProvider,
+                    zenModeController, lockscreenUserManager, groupManager, entryManager,
+                    notifPipeline, featureFlags, dumpController);
+            setInflateSynchronously(true);
+        }
+    }
+
+    static class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
+
+        TestableNotificationInterruptionStateProvider(Context context,
+                NotificationFilter filter, StatusBarStateController controller,
+                BatteryController batteryController) {
+            super(context, filter, controller, batteryController);
+            mUseHeadsUp = true;
+        }
+    }
+
+    /**
+     * Sets the bubble metadata flags for this entry. These flags are normally set by
+     * NotificationManagerService when the notification is sent, however, these tests do not
+     * go through that path so we set them explicitly when testing.
+     */
+    private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
+        Notification.BubbleMetadata bubbleMetadata =
+                entry.getSbn().getNotification().getBubbleMetadata();
+        int flags = bubbleMetadata.getFlags();
+        if (enableFlag) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+        bubbleMetadata.setFlags(flags);
+    }
+}
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/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
index a5722e2..4b47093 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -16,52 +16,116 @@
 
 package com.android.systemui.glwallpaper;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.RETURNS_DEFAULTS;
-import static org.mockito.Mockito.mock;
+import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.PixelFormat;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
+import android.view.SurfaceSession;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 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 org.mockito.Spy;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class EglHelperTest extends SysuiTestCase {
 
-    @Mock
+    @Spy
     private EglHelper mEglHelper;
+
     @Mock
     private SurfaceHolder mSurfaceHolder;
 
     @Before
     public void setUp() throws Exception {
-        mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS);
-        mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS);
+        MockitoAnnotations.initMocks(this);
+        prepareSurface();
+    }
+
+    @After
+    public void tearDown() {
+        mSurfaceHolder.getSurface().destroy();
+        mSurfaceHolder = null;
+    }
+
+    private void prepareSurface() {
+        final SurfaceSession session = new SurfaceSession();
+        final SurfaceControl control = new SurfaceControl.Builder(session)
+                .setName("Test")
+                .setBufferSize(100, 100)
+                .setFormat(PixelFormat.RGB_888)
+                .build();
+        final Surface surface = new Surface();
+        surface.copyFrom(control);
+        when(mSurfaceHolder.getSurface()).thenReturn(surface);
+        assertThat(mSurfaceHolder.getSurface()).isNotNull();
+        assertThat(mSurfaceHolder.getSurface().isValid()).isTrue();
     }
 
     @Test
     public void testInit_finish() {
         mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
+        assertThat(mEglHelper.hasEglDisplay()).isTrue();
+        assertThat(mEglHelper.hasEglContext()).isTrue();
+        assertThat(mEglHelper.hasEglSurface()).isTrue();
+        verify(mEglHelper).askCreatingEglWindowSurface(
+                any(SurfaceHolder.class), eq(null), anyInt());
+
+        mEglHelper.finish();
+        assertThat(mEglHelper.hasEglSurface()).isFalse();
+        assertThat(mEglHelper.hasEglContext()).isFalse();
+        assertThat(mEglHelper.hasEglDisplay()).isFalse();
+    }
+
+    @Test
+    public void testInit_finish_wide_gamut() {
+        // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
+        doReturn(0x3490).when(mEglHelper).getWcgCapability();
+        // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
+        doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace");
+        ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class);
+        // {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE}
+        final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038};
+
+        mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */);
+        verify(mEglHelper)
+                .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt());
+        assertThat(ac.getValue()).isNotNull();
+        assertThat(ac.getValue()).isEqualTo(expectedArgument);
         mEglHelper.finish();
     }
 
     @Test
     public void testFinish_shouldNotCrash() {
-        assertFalse(mEglHelper.hasEglDisplay());
-        assertFalse(mEglHelper.hasEglSurface());
-        assertFalse(mEglHelper.hasEglContext());
+        mEglHelper.terminateEglDisplay();
+        assertThat(mEglHelper.hasEglDisplay()).isFalse();
+        assertThat(mEglHelper.hasEglSurface()).isFalse();
+        assertThat(mEglHelper.hasEglContext()).isFalse();
 
         mEglHelper.finish();
+        verify(mEglHelper, never()).destroyEglContext();
+        verify(mEglHelper, never()).destroyEglSurface();
+        verify(mEglHelper, atMost(1)).terminateEglDisplay();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java
new file mode 100644
index 0000000..d881fd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.glwallpaper;
+
+import static com.android.systemui.glwallpaper.GLWallpaperRenderer.SurfaceProxy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.WallpaperManager;
+import android.app.WallpaperManager.ColorManagementProxy;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ImageWallpaperRendererTest extends SysuiTestCase {
+
+    private WallpaperManager mWpmSpy;
+    private SurfaceProxy mSurfaceProxy;
+
+    @Before
+    public void setUp() throws Exception {
+        final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class);
+        mWpmSpy = spy(wpm);
+        mContext.addMockSystemService(WallpaperManager.class, mWpmSpy);
+
+        mSurfaceProxy = new SurfaceProxy() {
+            @Override
+            public void requestRender() {
+                // NO-op
+            }
+
+            @Override
+            public void preRender() {
+                // No-op
+            }
+
+            @Override
+            public void postRender() {
+                // No-op
+            }
+        };
+    }
+
+    @Test
+    public void testWcgContent() throws IOException {
+        final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888,
+                false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+
+        final ColorManagementProxy proxy = new ColorManagementProxy(mContext);
+        final ColorManagementProxy cmProxySpy = spy(proxy);
+        final Set<ColorSpace> supportedWideGamuts = new HashSet<>();
+        supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+
+        try {
+            doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut();
+            doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy();
+            doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces();
+
+            mWpmSpy.setBitmap(p3Bitmap);
+            ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext, mSurfaceProxy);
+            assertThat(rendererP3.isWcgContent()).isTrue();
+
+            mWpmSpy.setBitmap(srgbBitmap);
+            ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext, mSurfaceProxy);
+            assertThat(renderer.isWcgContent()).isFalse();
+        } finally {
+            srgbBitmap.recycle();
+            p3Bitmap.recycle();
+        }
+    }
+
+}
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/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 4becd52..9fe2569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -88,8 +88,8 @@
     }
 
     @Test
-    fun testBooleanTileHasBooleanState() {
-        `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+    fun testToggleableTileHasBooleanState() {
+        `when`(mTileServiceManager.isToggleableTile).thenReturn(true)
         customTile = CustomTile.create(mTileHost, TILE_SPEC)
 
         assertTrue(customTile.state is QSTile.BooleanState)
@@ -104,7 +104,7 @@
 
     @Test
     fun testValueUpdatedInBooleanTile() {
-        `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+        `when`(mTileServiceManager.isToggleableTile).thenReturn(true)
         customTile = CustomTile.create(mTileHost, TILE_SPEC)
         customTile.qsTile.icon = mock(Icon::class.java)
         `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 9e5e582..42fd288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -105,7 +105,7 @@
             defaultServiceInfo = new ServiceInfo();
             defaultServiceInfo.metaData = new Bundle();
             defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true);
-            defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true);
+            defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, true);
         }
         when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt()))
                 .thenReturn(defaultServiceInfo);
@@ -244,7 +244,7 @@
     }
 
     @Test
-    public void testBooleanTile() throws Exception {
-        assertTrue(mStateManager.isBooleanTile());
+    public void testToggleableTile() throws Exception {
+        assertTrue(mStateManager.isToggleableTile());
     }
 }
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..5a0a495 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
@@ -58,10 +58,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.NotificationMessagingUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -79,16 +82,17 @@
 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.ExpandableNotificationRowController;
+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.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -96,15 +100,18 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
 
 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;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -137,11 +144,16 @@
     @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;
-    @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
+    @Mock private NotificationMediaManager mNotificationMediaManager;
+    @Mock private ExpandableNotificationRowComponent.Builder
+            mExpandableNotificationRowComponentBuilder;
+    @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
+    @Mock private FalsingManager mFalsingManager;
+    @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private StatusBarStateController mStatusBarStateController;
 
     private int mId;
     private NotificationEntry mEntry;
@@ -190,7 +202,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(SmartReplyController.class);
-        mDependency.injectMockDependency(NotificationMediaManager.class);
 
         mCountDownLatch = new CountDownLatch(1);
 
@@ -206,40 +217,35 @@
 
         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,
+                        new NotificationMessagingUtil(mContext),
                         mRemoteInputManager,
                         mLockscreenUserManager,
-                        contentBinder,
+                        mock(NotifBindPipeline.class),
+                        bindStage,
                         true, /* allowLongPress */
-                        mock(KeyguardBypassController.class),
-                        mock(StatusBarStateController.class),
+                        mKeyguardBypassController,
+                        mStatusBarStateController,
                         mGroupManager,
                         mGutsManager,
                         mNotificationInterruptionStateProvider,
-                        () -> new RowInflaterTask(mNotificationRowComponentBuilder),
-                        mock(NotificationLogger.class));
+                        RowInflaterTask::new,
+                        mExpandableNotificationRowComponentBuilder);
 
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
-                mNotifLog,
+                mLogger,
                 mGroupManager,
                 new NotificationRankingManager(
-                        () -> mock(NotificationMediaManager.class),
+                        () -> mNotificationMediaManager,
                         mGroupManager,
                         mHeadsUpManager,
                         mock(NotificationFilter.class),
-                        mNotifLog,
+                        mLogger,
                         mock(NotificationSectionsFeatureManager.class),
                         mock(PeopleNotificationIdentifier.class),
                         mock(HighPriorityProvider.class)),
@@ -254,13 +260,55 @@
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
 
-        notificationRowBinder.setUpWithPresenter(
-                mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
+        notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
         notificationRowBinder.setInflationCallback(mEntryManager);
         notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
 
         setUserSentiment(
                 mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
+
+        ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
+                ArgumentCaptor.forClass(ExpandableNotificationRow.class);
+        when(mExpandableNotificationRowComponentBuilder
+                .expandableNotificationRow(viewCaptor.capture()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+        when(mExpandableNotificationRowComponentBuilder
+                .notificationEntry(any()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+        when(mExpandableNotificationRowComponentBuilder
+                .onDismissRunnable(any()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+        when(mExpandableNotificationRowComponentBuilder
+                .inflationCallback(any()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+        when(mExpandableNotificationRowComponentBuilder
+                .onExpandClickListener(any()))
+                .thenReturn(mExpandableNotificationRowComponentBuilder);
+
+        when(mExpandableNotificationRowComponentBuilder.build())
+                .thenReturn(mExpandableNotificationRowComponent);
+        when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
+                .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
+                        new ExpandableNotificationRowController(
+                                viewCaptor.getValue(),
+                                mock(ActivatableNotificationViewController.class),
+                                mNotificationMediaManager,
+                                mock(PluginManager.class),
+                                new FakeSystemClock(),
+                                "FOOBAR", "FOOBAR",
+                                mKeyguardBypassController,
+                                mGroupManager,
+                                bindStage,
+                                mock(NotificationLogger.class),
+                                mHeadsUpManager,
+                                mPresenter,
+                                mStatusBarStateController,
+                                mEntryManager,
+                                mGutsManager,
+                                true,
+                                null,
+                                mFalsingManager
+                        ));
     }
 
     @After
@@ -269,7 +317,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 +357,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -331,6 +383,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification_prePostEntryOrder() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -399,7 +452,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/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 7c94ed20..abc0f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -47,6 +48,7 @@
 import android.annotation.Nullable;
 import android.os.RemoteException;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationStats;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
@@ -69,6 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.Assert;
 
@@ -98,11 +101,19 @@
     @Spy private RecordingCollectionListener mCollectionListener;
     @Mock private CollectionReadyForBuildListener mBuildListener;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private DismissedByUserStats mDismissedByUserStats;
 
     @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
     @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
     @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3");
 
+    @Spy private RecordingDismissInterceptor mInterceptor1 = new RecordingDismissInterceptor(
+            "Interceptor1");
+    @Spy private RecordingDismissInterceptor mInterceptor2 = new RecordingDismissInterceptor(
+            "Interceptor2");
+    @Spy private RecordingDismissInterceptor mInterceptor3 = new RecordingDismissInterceptor(
+            "Interceptor3");
+
     @Captor private ArgumentCaptor<BatchableNotificationHandler> mListenerCaptor;
     @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor;
     @Captor private ArgumentCaptor<Collection<NotificationEntry>> mBuildListCaptor;
@@ -441,6 +452,169 @@
         assertEquals(NOT_DISMISSED, entry3.getDismissState());
     }
 
+    @Test
+    public void testDismissInterceptorsAreCalled() throws RemoteException {
+        // GIVEN a collection with notifications with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+        // WHEN a notification is manually dismissed
+        DismissedByUserStats stats = new DismissedByUserStats(
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        mCollection.dismissNotification(entry, stats);
+
+        // THEN all interceptors get checked
+        verify(mInterceptor1).shouldInterceptDismissal(entry);
+        verify(mInterceptor2).shouldInterceptDismissal(entry);
+        verify(mInterceptor3).shouldInterceptDismissal(entry);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
+
+        // THEN we never send the dismissal to system server
+        verify(mStatusBarService, never()).onNotificationClear(
+                notif.sbn.getPackageName(),
+                notif.sbn.getTag(),
+                47,
+                notif.sbn.getUser().getIdentifier(),
+                notif.sbn.getKey(),
+                stats.dismissalSurface,
+                stats.dismissalSentiment,
+                stats.notificationVisibility);
+    }
+
+    @Test
+    public void testDismissInterceptorsCanceledWhenNotifIsUpdated() throws RemoteException {
+        // GIVEN a few lifetime extenders and a couple notifications
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+        // WHEN a notification is manually dismissed and intercepted
+        DismissedByUserStats stats = new DismissedByUserStats(
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        mCollection.dismissNotification(entry, stats);
+        assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
+        clearInvocations(mInterceptor1, mInterceptor2);
+
+        // WHEN the notification is reposted
+        mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+
+        // THEN all of the active dismissal interceptors are canceled
+        verify(mInterceptor1).cancelDismissInterception(entry);
+        verify(mInterceptor2).cancelDismissInterception(entry);
+        assertEquals(List.of(), entry.mDismissInterceptors);
+
+        // THEN the notification is never sent to system server to dismiss
+        verify(mStatusBarService, never()).onNotificationClear(
+                eq(notif.sbn.getPackageName()),
+                eq(notif.sbn.getTag()),
+                eq(47),
+                eq(notif.sbn.getUser().getIdentifier()),
+                eq(notif.sbn.getKey()),
+                anyInt(),
+                anyInt(),
+                anyObject());
+    }
+
+    @Test
+    public void testEndingAllDismissInterceptorsSendsDismiss() throws RemoteException {
+        // GIVEN a collection with notifications a dismiss interceptor
+        mInterceptor1.shouldInterceptDismissal = true;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+        // GIVEN a notification is manually dismissed
+        DismissedByUserStats stats = new DismissedByUserStats(
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        mCollection.dismissNotification(entry, stats);
+
+        // WHEN all interceptors end their interception dismissal
+        mInterceptor1.shouldInterceptDismissal = false;
+        mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
+                mDismissedByUserStats);
+
+        // THEN we send the dismissal to system server
+        verify(mStatusBarService, times(1)).onNotificationClear(
+                eq(notif.sbn.getPackageName()),
+                eq(notif.sbn.getTag()),
+                eq(47),
+                eq(notif.sbn.getUser().getIdentifier()),
+                eq(notif.sbn.getKey()),
+                anyInt(),
+                anyInt(),
+                anyObject());
+    }
+
+    @Test
+    public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException {
+        // GIVEN a collection with notifications with multiple dismiss interceptors
+        mInterceptor1.shouldInterceptDismissal = true;
+        mInterceptor2.shouldInterceptDismissal = true;
+        mInterceptor3.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+        mCollection.addNotificationDismissInterceptor(mInterceptor2);
+        mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+        // GIVEN a notification is manually dismissed
+        DismissedByUserStats stats = new DismissedByUserStats(
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+        mCollection.dismissNotification(entry, stats);
+
+       // WHEN an interceptor ends its interception
+        mInterceptor1.shouldInterceptDismissal = false;
+        mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
+                mDismissedByUserStats);
+
+        // THEN all interceptors get checked
+        verify(mInterceptor1).shouldInterceptDismissal(entry);
+        verify(mInterceptor2).shouldInterceptDismissal(entry);
+        verify(mInterceptor3).shouldInterceptDismissal(entry);
+
+        // THEN mInterceptor2 is the only dismiss interceptor
+        assertEquals(List.of(mInterceptor2), entry.mDismissInterceptors);
+    }
+
+
+    @Test(expected = IllegalStateException.class)
+    public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException {
+        // GIVEN a collection with notifications with a dismiss interceptor that hasn't been called
+        mInterceptor1.shouldInterceptDismissal = false;
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+        // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
+        mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
+                mDismissedByUserStats);
+
+        // THEN an exception is thrown
+    }
+
     @Test(expected = IllegalStateException.class)
     public void testDismissingNonExistentNotificationThrows() {
         // GIVEN a collection that originally had three notifs, but where one was dismissed
@@ -894,6 +1068,36 @@
         }
     }
 
+    private static class RecordingDismissInterceptor implements NotifDismissInterceptor {
+        private final String mName;
+
+        public @Nullable OnEndDismissInterception onEndInterceptionCallback;
+        public boolean shouldInterceptDismissal = false;
+
+        private RecordingDismissInterceptor(String name) {
+            mName = name;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public void setCallback(OnEndDismissInterception callback) {
+            this.onEndInterceptionCallback = callback;
+        }
+
+        @Override
+        public boolean shouldInterceptDismissal(NotificationEntry entry) {
+            return shouldInterceptDismissal;
+        }
+
+        @Override
+        public void cancelDismissInterception(NotificationEntry entry) {
+        }
+    }
+
     private static final String TEST_PACKAGE = "com.android.test.collection";
     private static final String TEST_PACKAGE2 = "com.android.test.collection2";
 
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..a891810 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);
@@ -205,9 +194,8 @@
     @Test
     public void testClickSound() throws Exception {
         assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
-        StatusBarStateController mock = mock(StatusBarStateController.class);
+        StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
         when(mock.isDozing()).thenReturn(true);
-        mGroupRow.setStatusBarStateController(mock);
         mGroupRow.setSecureStateProvider(()-> false);
         assertFalse("Shouldn't play sounds when dark and trusted.",
                 mGroupRow.isSoundEffectsEnabled());
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/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 81%
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 457bbe23..35b5508 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,21 @@
 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.FalsingManager;
 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 +63,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 +89,10 @@
     private final NotificationGroupManager mGroupManager;
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
+    private final NotifBindPipeline mBindPipeline;
+    private final NotificationEntryListener mBindPipelineEntryListener;
+    private final RowContentBindStage mBindStage;
+    private StatusBarStateController mStatusBarStateController;
 
     public NotificationTestHelper(Context context, TestableDependency dependency) {
         mContext = context;
@@ -89,12 +100,29 @@
         dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(SmartReplyController.class);
-        StatusBarStateController stateController = mock(StatusBarStateController.class);
-        mGroupManager = new NotificationGroupManager(stateController);
-        mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController,
+        mStatusBarStateController = mock(StatusBarStateController.class);
+        mGroupManager = new NotificationGroupManager(mStatusBarStateController);
+        mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
                 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();
     }
 
     /**
@@ -173,9 +201,17 @@
     /**
      * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
      */
+    public ExpandableNotificationRow createBubbleInGroup()
+            throws Exception {
+        return createBubble(makeBubbleMetadata(null), PKG, true);
+    }
+
+    /**
+     * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+     */
     public ExpandableNotificationRow createBubble()
             throws Exception {
-        return createBubble(makeBubbleMetadata(null), PKG);
+        return createBubble(makeBubbleMetadata(null), PKG, false);
     }
 
     /**
@@ -185,7 +221,7 @@
      */
     public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent)
             throws Exception {
-        return createBubble(makeBubbleMetadata(deleteIntent), PKG);
+        return createBubble(makeBubbleMetadata(deleteIntent), PKG, false);
     }
 
     /**
@@ -195,8 +231,14 @@
      */
     public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg)
             throws Exception {
+        return createBubble(bubbleMetadata, pkg, false);
+    }
+
+    private ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg,
+            boolean inGroup)
+            throws Exception {
         Notification n = createNotification(false /* isGroupSummary */,
-                null /* groupKey */, bubbleMetadata);
+                inGroup ? GROUP_KEY : null /* groupKey */, bubbleMetadata);
         n.flags |= FLAG_BUBBLE;
         ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE,
                 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
@@ -281,6 +323,10 @@
         return notificationBuilder.build();
     }
 
+    public StatusBarStateController getStatusBarStateController() {
+        return mStatusBarStateController;
+    }
+
     private ExpandableNotificationRow generateRow(
             Notification notification,
             String pkg,
@@ -331,10 +377,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 +387,15 @@
                 mock(KeyguardBypassController.class),
                 mGroupManager,
                 mHeadsUpManager,
-                contentBinder,
-                mock(OnExpandClickListener.class));
+                mBindStage,
+                mock(OnExpandClickListener.class),
+                mock(NotificationMediaManager.class),
+                mock(ExpandableNotificationRow.OnAppOpsClickListener.class),
+                mock(FalsingManager.class),
+                mStatusBarStateController);
         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 +404,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..2d1bc78 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
@@ -30,12 +30,11 @@
 import androidx.test.filters.SmallTest;
 
 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;
 
@@ -59,8 +58,6 @@
     private ExpandableNotificationRow mFirst;
     private ExpandableNotificationRow mSecond;
     @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
     private KeyguardBypassController mBypassController;
 
     @Before
@@ -150,13 +147,12 @@
                 createSection(mFirst, mSecond),
                 createSection(null, null)
         });
-        ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency)
-                .createRow();
+        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
+        ExpandableNotificationRow row = testHelper.createRow();
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.getRow()).thenReturn(row);
 
-        when(mStatusBarStateController.isDozing()).thenReturn(true);
-        row.setStatusBarStateController(mStatusBarStateController);
+        when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true);
         row.setHeadsUp(true);
         mRoundnessManager.onHeadsUpStateChanged(entry, true);
         Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f);
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/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 631c580..cd91f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+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.eq;
@@ -24,10 +26,12 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.net.ConnectivityManager;
+import android.net.TetheringManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -38,11 +42,14 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.concurrent.Executor;
 
 @SmallTest
@@ -51,13 +58,19 @@
 public class HotspotControllerImplTest extends SysuiTestCase {
 
     @Mock
-    private ConnectivityManager mConnectivityManager;
+    private TetheringManager mTetheringManager;
     @Mock
     private WifiManager mWifiManager;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private HotspotController.Callback mCallback1;
     @Mock
     private HotspotController.Callback mCallback2;
+    @Mock
+    private TetheringManager.TetheringInterfaceRegexps mTetheringInterfaceRegexps;
+    @Captor
+    private ArgumentCaptor<TetheringManager.TetheringEventCallback> mTetheringCallbackCaptor;
     private HotspotControllerImpl mController;
     private TestableLooper mLooper;
 
@@ -66,8 +79,13 @@
         MockitoAnnotations.initMocks(this);
         mLooper = TestableLooper.get(this);
 
-        mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
         mContext.addMockSystemService(WifiManager.class, mWifiManager);
+        mContext.addMockSystemService(TetheringManager.class, mTetheringManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+
+        when(mUserManager.isUserAdmin(anyInt())).thenReturn(true);
+        when(mTetheringInterfaceRegexps.getTetherableWifiRegexs()).thenReturn(
+                Collections.singletonList("test"));
 
         doAnswer((InvocationOnMock invocation) -> {
             ((WifiManager.SoftApCallback) invocation.getArgument(1))
@@ -76,7 +94,11 @@
         }).when(mWifiManager).registerSoftApCallback(any(Executor.class),
                 any(WifiManager.SoftApCallback.class));
 
-        mController = new HotspotControllerImpl(mContext, new Handler(mLooper.getLooper()));
+        Handler handler = new Handler(mLooper.getLooper());
+
+        mController = new HotspotControllerImpl(mContext, handler, handler);
+        verify(mTetheringManager)
+                .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
     }
 
     @Test
@@ -117,4 +139,28 @@
         verify(mWifiManager, never()).unregisterSoftApCallback(any());
     }
 
+    @Test
+    public void testDefault_hotspotNotSupported() {
+        assertFalse(mController.isHotspotSupported());
+    }
+
+    @Test
+    public void testHotspotSupported_rightConditions() {
+        mTetheringCallbackCaptor.getValue().onTetheringSupported(true);
+        mTetheringCallbackCaptor.getValue()
+                .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps);
+
+        assertTrue(mController.isHotspotSupported());
+    }
+
+    @Test
+    public void testHotspotSupported_callbackCalledOnChange() {
+        mController.addCallback(mCallback1);
+        mTetheringCallbackCaptor.getValue().onTetheringSupported(true);
+        mTetheringCallbackCaptor.getValue()
+                .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps);
+
+        verify(mCallback1).onHotspotAvailabilityChanged(true);
+    }
+
 }
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/Android.bp b/packages/Tethering/Android.bp
index cf2b1f06..4efe934 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -28,6 +28,7 @@
         "netd_aidl_interface-unstable-java",
         "netlink-client",
         "networkstack-aidl-interfaces-unstable-java",
+        "android.hardware.tetheroffload.config-V1.0-java",
         "android.hardware.tetheroffload.control-V1.0-java",
         "net-utils-framework-common",
     ],
@@ -48,26 +49,26 @@
 // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
 cc_library {
     name: "libtetherutilsjni",
+    sdk_version: "current",
     srcs: [
         "jni/android_net_util_TetheringUtils.cpp",
     ],
     shared_libs: [
-        "libcgrouprc",
-        "libnativehelper_compat_libc++",
-        "libvndksupport",
-    ],
-    static_libs: [
-        "android.hardware.tetheroffload.config@1.0",
         "liblog",
-        "libbase",
-        "libbinderthreadstate",
-        "libcutils",
-        "libhidlbase",
-        "libjsoncpp",
-        "libprocessgroup",
-        "libutils",
+        "libnativehelper_compat_libc++",
     ],
 
+    // We cannot use plain "libc++" here to link libc++ dynamically because it results in:
+    //   java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
+    // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't
+    // build because soong complains of:
+    //   module Tethering missing dependencies: libc++_shared
+    //
+    // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries
+    // we depend on do not dynamically link libc++. This is currently the case, because liblog is
+    // C-only and libnativehelper_compat_libc also uses stl: "c++_static".
+    stl: "c++_static",
+
     cflags: [
         "-Wall",
         "-Werror",
@@ -86,9 +87,8 @@
     // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
     // explicitly.
     jni_libs: [
-        "libcgrouprc",
+        "liblog",
         "libnativehelper_compat_libc++",
-        "libvndksupport",
         "libtetherutilsjni",
     ],
     resource_dirs: [
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index e0adb34d..8c4f733 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -59,16 +59,33 @@
     ],
 
     hostdex: true, // for hiddenapi check
-    visibility: [
-        "//frameworks/base/packages/Tethering:__subpackages__",
-        //TODO(b/147200698) remove below lines when the platform is built with stubs
-        "//frameworks/base",
-        "//frameworks/base/services",
-        "//frameworks/base/services/core",
-    ],
+    visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
     apex_available: ["com.android.tethering"],
 }
 
+droidstubs {
+    name: "framework-tethering-stubs-sources",
+    defaults: ["framework-module-stubs-defaults-module_libs_api"],
+    srcs: [
+        "src/android/net/TetheredClient.java",
+        "src/android/net/TetheringManager.java",
+        "src/android/net/TetheringConstants.java",
+    ],
+    libs: [
+        "tethering-aidl-interfaces-java",
+        "framework-all",
+    ],
+    sdk_version: "core_platform",
+}
+
+java_library {
+    name: "framework-tethering-stubs",
+    srcs: [":framework-tethering-stubs-sources"],
+    libs: ["framework-all"],
+    static_libs: ["tethering-aidl-interfaces-java"],
+    sdk_version: "core_platform",
+}
+
 filegroup {
     name: "framework-tethering-srcs",
     srcs: [
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
index 6514688..ca5ef09 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -34,6 +36,7 @@
  * @hide
  */
 @SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
 @TestApi
 public final class TetheredClient implements Parcelable {
     @NonNull
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index 00cf98e..df87ac9 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
 import android.os.ResultReceiver;
 
 /**
@@ -28,39 +31,30 @@
  * symbols from framework-tethering even when they are in a non-hidden class.
  * @hide
  */
+@SystemApi(client = MODULE_LIBRARIES)
 public class TetheringConstants {
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering to
      * enable if any.
-     *
-     * {@hide}
      */
     public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering for
      * which to cancel provisioning.
-     *
-     * {@hide}
      */
     public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
     /**
      * Extra used for communicating with the TetherService. True to schedule a recheck of tether
      * provisioning.
-     *
-     * {@hide}
      */
     public static final String EXTRA_SET_ALARM = "extraSetAlarm";
     /**
      * Tells the TetherService to run a provision check now.
-     *
-     * {@hide}
      */
     public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
     /**
      * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
      * which will receive provisioning results. Can be left empty.
-     *
-     * {@hide}
      */
     public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 53a358f..6a9f010 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,6 +15,8 @@
  */
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -50,6 +52,7 @@
  * @hide
  */
 @SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
 @TestApi
 public class TetheringManager {
     private static final String TAG = TetheringManager.class.getSimpleName();
@@ -177,6 +180,7 @@
      *                          service is not connected.
      * {@hide}
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public TetheringManager(@NonNull final Context context,
             @NonNull Supplier<IBinder> connectorSupplier) {
         mContext = context;
@@ -395,6 +399,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int tether(@NonNull final String iface) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "tether caller:" + callerPkg);
@@ -418,6 +423,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int untether(@NonNull final String iface) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "untether caller:" + callerPkg);
@@ -444,6 +450,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int setUsbTethering(final boolean enable) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "setUsbTethering caller:" + callerPkg);
@@ -702,6 +709,7 @@
      * {@hide}
      */
     // TODO: improve the usage of ResultReceiver, b/145096122
+    @SystemApi(client = MODULE_LIBRARIES)
     public void requestLatestTetheringEntitlementResult(final int type,
             @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
         final String callerPkg = mContext.getOpPackageName();
@@ -982,6 +990,7 @@
      *               interface
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public int getLastTetherError(@NonNull final String iface) {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
@@ -1004,6 +1013,7 @@
      *        what interfaces are considered tetherable usb interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableUsbRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableUsbRegexs;
@@ -1018,6 +1028,7 @@
      *        what interfaces are considered tetherable wifi interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableWifiRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableWifiRegexs;
@@ -1032,6 +1043,7 @@
      *        what interfaces are considered tetherable bluetooth interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableBluetoothRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableBluetoothRegexs;
@@ -1044,6 +1056,7 @@
      * @return an array of 0 or more Strings of tetherable interface names.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1057,6 +1070,7 @@
      * @return an array of 0 or more String of currently tethered interface names.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetheredIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1076,6 +1090,7 @@
      *        which failed to tether.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetheringErroredIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1103,6 +1118,7 @@
      * @return a boolean - {@code true} indicating Tethering is supported.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public boolean isTetheringSupported() {
         final String callerPkg = mContext.getOpPackageName();
 
diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
index 1cf8f98..54934406 100644
--- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
+++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -16,123 +16,18 @@
 
 #include <errno.h>
 #include <error.h>
-#include <hidl/HidlSupport.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <linux/netfilter/nfnetlink.h>
-#include <linux/netlink.h>
 #include <net/if.h>
 #include <netinet/icmp6.h>
 #include <sys/socket.h>
-#include <android-base/unique_fd.h>
-#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
 
 #define LOG_TAG "TetheringUtils"
-#include <utils/Log.h>
+#include <android/log.h>
 
 namespace android {
 
-using hardware::hidl_handle;
-using hardware::hidl_string;
-using hardware::tetheroffload::config::V1_0::IOffloadConfig;
-
-namespace {
-
-inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
-    return reinterpret_cast<const sockaddr *>(nladdr);
-}
-
-int conntrackSocket(unsigned groups) {
-    base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
-    if (s.get() < 0) return -errno;
-
-    const struct sockaddr_nl bind_addr = {
-        .nl_family = AF_NETLINK,
-        .nl_pad = 0,
-        .nl_pid = 0,
-        .nl_groups = groups,
-    };
-    if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
-        return -errno;
-    }
-
-    const struct sockaddr_nl kernel_addr = {
-        .nl_family = AF_NETLINK,
-        .nl_pad = 0,
-        .nl_pid = 0,
-        .nl_groups = groups,
-    };
-    if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
-        return -errno;
-    }
-
-    return s.release();
-}
-
-// Return a hidl_handle that owns the file descriptor owned by fd, and will
-// auto-close it (otherwise there would be double-close problems).
-//
-// Rely upon the compiler to eliminate the constexprs used for clarity.
-hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
-    hidl_handle h;
-
-    static constexpr int kNumFds = 1;
-    static constexpr int kNumInts = 0;
-    native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
-    nh->data[0] = fd.release();
-
-    static constexpr bool kTakeOwnership = true;
-    h.setTo(nh, kTakeOwnership);
-
-    return h;
-}
-
-}  // namespace
-
-static jboolean android_net_util_configOffload(
-        JNIEnv* /* env */) {
-    sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
-    if (configInterface.get() == nullptr) {
-        ALOGD("Could not find IOffloadConfig service.");
-        return false;
-    }
-
-    // Per the IConfigOffload definition:
-    //
-    // fd1   A file descriptor bound to the following netlink groups
-    //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
-    //
-    // fd2   A file descriptor bound to the following netlink groups
-    //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
-    base::unique_fd
-            fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
-            fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
-    if (fd1.get() < 0 || fd2.get() < 0) {
-        ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
-        return false;
-    }
-
-    hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
-                h2(handleFromFileDescriptor(std::move(fd2)));
-
-    bool rval(false);
-    hidl_string msg;
-    const auto status = configInterface->setHandles(h1, h2,
-            [&rval, &msg](bool success, const hidl_string& errMsg) {
-                rval = success;
-                msg = errMsg;
-            });
-    if (!status.isOk() || !rval) {
-        ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
-              status.description().c_str(), msg.c_str());
-        // If status is somehow not ok, make sure rval captures this too.
-        rval = false;
-    }
-
-    return rval;
-}
-
 static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
         jint ifIndex)
 {
@@ -229,7 +124,6 @@
  */
 static const JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
-    { "configOffload", "()Z", (void*) android_net_util_configOffload },
     { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket },
 };
 
@@ -242,7 +136,7 @@
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        ALOGE("ERROR: GetEnv failed");
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
         return JNI_ERR;
     }
 
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index fa543bd..5a6d5c1 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -24,14 +24,6 @@
  * {@hide}
  */
 public class TetheringUtils {
-
-    /**
-     * Offload management process need to know conntrack rules to support NAT, but it may not have
-     * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
-     * share them with offload management process.
-     */
-    public static native boolean configOffload();
-
     /**
      * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
      * @param fd the socket's {@link FileDescriptor}.
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 90b9d3f..b545717 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -18,19 +18,28 @@
 
 import static android.net.util.TetheringUtils.uint16;
 
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.NetlinkSocket;
 import android.net.util.SharedLog;
-import android.net.util.TetheringUtils;
+import android.net.util.SocketUtils;
 import android.os.Handler;
+import android.os.NativeHandle;
 import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.SocketException;
 import java.util.ArrayList;
 
 
@@ -49,6 +58,10 @@
     private static final String NO_INTERFACE_NAME = "";
     private static final String NO_IPV4_ADDRESS = "";
     private static final String NO_IPV4_GATEWAY = "";
+    // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
+    private static final int NF_NETLINK_CONNTRACK_NEW = 1;
+    private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+    private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
 
     private final Handler mHandler;
     private final SharedLog mLog;
@@ -121,9 +134,103 @@
         return DEFAULT_TETHER_OFFLOAD_DISABLED;
     }
 
-    /** Configure offload management process. */
+    /**
+     * Offload management process need to know conntrack rules to support NAT, but it may not have
+     * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
+     * share them with offload management process.
+     */
     public boolean initOffloadConfig() {
-        return TetheringUtils.configOffload();
+        IOffloadConfig offloadConfig;
+        try {
+            offloadConfig = IOffloadConfig.getService();
+        } catch (RemoteException e) {
+            mLog.e("getIOffloadConfig error " + e);
+            return false;
+        }
+        if (offloadConfig == null) {
+            mLog.e("Could not find IOffloadConfig service");
+            return false;
+        }
+        // Per the IConfigOffload definition:
+        //
+        // h1    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+        //
+        // h2    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+        final NativeHandle h1 = createConntrackSocket(
+                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+        if (h1 == null) return false;
+
+        final NativeHandle h2 = createConntrackSocket(
+                NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
+        if (h2 == null) {
+            closeFdInNativeHandle(h1);
+            return false;
+        }
+
+        final CbResults results = new CbResults();
+        try {
+            offloadConfig.setHandles(h1, h2,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record("initOffloadConfig, setHandles fail", e);
+            return false;
+        }
+        // Explicitly close FDs.
+        closeFdInNativeHandle(h1);
+        closeFdInNativeHandle(h2);
+
+        record("initOffloadConfig, setHandles results:", results);
+        return results.mSuccess;
+    }
+
+    private void closeFdInNativeHandle(final NativeHandle h) {
+        try {
+            h.close();
+        } catch (IOException | IllegalStateException e) {
+            // IllegalStateException means fd is already closed, do nothing here.
+            // Also nothing we can do if IOException.
+        }
+    }
+
+    private NativeHandle createConntrackSocket(final int groups) {
+        FileDescriptor fd;
+        try {
+            fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+        } catch (ErrnoException e) {
+            mLog.e("Unable to create conntrack socket " + e);
+            return null;
+        }
+
+        final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
+        try {
+            Os.bind(fd, sockAddr);
+        } catch (ErrnoException | SocketException e) {
+            mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
+            try {
+                SocketUtils.closeSocket(fd);
+            } catch (IOException ie) {
+                // Nothing we can do here
+            }
+            return null;
+        }
+        try {
+            Os.connect(fd, sockAddr);
+        } catch (ErrnoException | SocketException e) {
+            mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
+            try {
+                SocketUtils.closeSocket(fd);
+            } catch (IOException ie) {
+                // Nothing we can do here
+            }
+            return null;
+        }
+
+        return new NativeHandle(fd, true);
     }
 
     /** Initialize the tethering offload HAL. */
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..64c16e4 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();
@@ -1943,7 +1944,8 @@
             parcel.tetheringSupported = mDeps.isTetheringSupported();
             parcel.upstreamNetwork = mTetherUpstream;
             parcel.config = mConfig.toStableParcelable();
-            parcel.states = mTetherStatesParcel;
+            parcel.states =
+                    mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel();
             try {
                 callback.onCallbackStarted(parcel);
             } catch (RemoteException e) {
@@ -1952,6 +1954,17 @@
         });
     }
 
+    private TetherStatesParcel emptyTetherStatesParcel() {
+        final TetherStatesParcel parcel = new TetherStatesParcel();
+        parcel.availableList = new String[0];
+        parcel.tetheredList = new String[0];
+        parcel.localOnlyList = new String[0];
+        parcel.erroredIfaceList = new String[0];
+        parcel.lastErrorList = new int[0];
+
+        return parcel;
+    }
+
     /** Unregister tethering event callback */
     void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
         mHandler.post(() -> {
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 13174c5..c6905ec 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -34,7 +34,15 @@
         "TetheringApiCurrentLib",
         "testables",
     ],
+    // TODO(b/147200698) change sdk_version to module-current and
+    // remove framework-minus-apex, ext, and framework-res
+    sdk_version: "core_platform",
     libs: [
+        "framework-minus-apex",
+        "ext",
+        "framework-res",
+        "framework-wifi-stubs",
+        "framework-telephony-stubs",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 4710287..6d49e20 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -127,6 +127,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.networkstack.tethering.R;
+import com.android.testutils.MiscAssertsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -1220,6 +1221,16 @@
         }
     }
 
+    private void assertTetherStatesNotNullButEmpty(final TetherStatesParcel parcel) {
+        assertFalse(parcel == null);
+        assertEquals(0, parcel.availableList.length);
+        assertEquals(0, parcel.tetheredList.length);
+        assertEquals(0, parcel.localOnlyList.length);
+        assertEquals(0, parcel.erroredIfaceList.length);
+        assertEquals(0, parcel.lastErrorList.length);
+        MiscAssertsKt.assertFieldCountEquals(5, TetherStatesParcel.class);
+    }
+
     @Test
     public void testRegisterTetheringEventCallback() throws Exception {
         TestTetheringEventCallback callback = new TestTetheringEventCallback();
@@ -1232,7 +1243,7 @@
         callback.expectConfigurationChanged(
                 mTethering.getTetheringConfiguration().toStableParcelable());
         TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
-        assertEquals(tetherState, null);
+        assertTetherStatesNotNullButEmpty(tetherState);
         // 2. Enable wifi tethering.
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
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/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
index c67fe9f..1e8109c 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
@@ -23,6 +23,8 @@
 public class PacNative {
     private static final String TAG = "PacProxy";
 
+    private static final PacNative sInstance = new PacNative();
+
     private String mCurrentPac;
 
     private boolean mIsActive;
@@ -39,10 +41,14 @@
         System.loadLibrary("jni_pacprocessor");
     }
 
-    PacNative() {
+    private PacNative() {
 
     }
 
+    public static PacNative getInstance() {
+        return sInstance;
+    }
+
     public synchronized boolean startPacSupport() {
         if (createV8ParserNativeLocked()) {
             Log.e(TAG, "Unable to Create v8 Proxy Parser.");
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 74391eb..b006d6e 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -31,43 +31,27 @@
 public class PacService extends Service {
     private static final String TAG = "PacService";
 
-    private PacNative mPacNative;
-    private ProxyServiceStub mStub;
+    private PacNative mPacNative = PacNative.getInstance();
+    private ProxyServiceStub mStub = new ProxyServiceStub();
 
     @Override
     public void onCreate() {
         super.onCreate();
-        if (mPacNative == null) {
-            mPacNative = new PacNative();
-            mStub = new ProxyServiceStub(mPacNative);
-        }
+        mPacNative.startPacSupport();
     }
 
     @Override
     public void onDestroy() {
+        mPacNative.stopPacSupport();
         super.onDestroy();
-        if (mPacNative != null) {
-            mPacNative.stopPacSupport();
-            mPacNative = null;
-            mStub = null;
-        }
     }
 
     @Override
     public IBinder onBind(Intent intent) {
-        if (mPacNative == null) {
-            mPacNative = new PacNative();
-            mStub = new ProxyServiceStub(mPacNative);
-        }
         return mStub;
     }
 
-    private static class ProxyServiceStub extends IProxyService.Stub {
-        private final PacNative mPacNative;
-
-        public ProxyServiceStub(PacNative pacNative) {
-            mPacNative = pacNative;
-        }
+    private class ProxyServiceStub extends IProxyService.Stub {
 
         @Override
         public String resolvePacFile(String host, String url) throws RemoteException {
@@ -102,20 +86,12 @@
 
         @Override
         public void startPacSystem() throws RemoteException {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                Log.e(TAG, "Only system user is allowed to call startPacSystem");
-                throw new SecurityException();
-            }
-            mPacNative.startPacSupport();
+            //TODO: remove
         }
 
         @Override
         public void stopPacSystem() throws RemoteException {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                Log.e(TAG, "Only system user is allowed to call stopPacSystem");
-                throw new SecurityException();
-            }
-            mPacNative.stopPacSupport();
+            //TODO: remove
         }
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 28c8aee..416f448 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -30,6 +30,7 @@
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
         ":service-permission-sources",
+        ":service-statsd-sources",
     ],
     visibility: ["//visibility:private"],
 }
@@ -75,7 +76,7 @@
 
     libs: [
         "android.hidl.manager-V1.0-java",
-        "framework-tethering"
+        "framework-tethering-stubs",
     ],
 
     plugins: [
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..4474f60 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -82,6 +82,7 @@
 import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
+import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.infra.AbstractPerUserSystemService;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
@@ -180,6 +181,8 @@
 
     private final InputMethodManagerInternal mInputMethodManagerInternal;
 
+    private final ContentCaptureManagerInternal mContentCaptureManagerInternal;
+
     AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
             LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
             AutofillCompatState autofillCompatState,
@@ -192,10 +195,22 @@
         mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId);
         mAutofillCompatState = autofillCompatState;
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+        mContentCaptureManagerInternal = LocalServices.getService(
+                ContentCaptureManagerInternal.class);
 
         updateLocked(disabled);
     }
 
+    boolean sendActivityAssistDataToContentCapture(@NonNull IBinder activityToken,
+            @NonNull Bundle data) {
+        if (mContentCaptureManagerInternal != null) {
+            mContentCaptureManagerInternal.sendActivityAssistData(getUserId(), activityToken, data);
+            return true;
+        }
+
+        return false;
+    }
+
     @GuardedBy("mLock")
     void onBackKeyPressed() {
         final RemoteAugmentedAutofillService remoteService =
@@ -818,27 +833,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 +1241,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/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
deleted file mode 100644
index cb6c8f5..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static com.android.server.autofill.Helper.sDebug;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.InlinePresentation;
-import android.util.Slog;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestion;
-import android.view.inputmethod.InlineSuggestionInfo;
-import android.view.inputmethod.InlineSuggestionsResponse;
-
-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";
-
-    /**
-     * Callback from the inline suggestion Ui.
-     */
-    public interface InlineSuggestionUiCallback {
-        /**
-         * Callback to autofill a dataset to the client app.
-         */
-        void autofill(@NonNull Dataset dataset);
-    }
-
-    /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by
-     * augmented autofill service.
-     */
-    public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
-            @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);
-    }
-
-    /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
-     * autofill service.
-     */
-    public static InlineSuggestionsResponse createInlineSuggestionsResponse(int requestId,
-            @NonNull Dataset[] datasets,
-            @NonNull AutofillId autofillId,
-            @NonNull Context context,
-            @NonNull AutoFillUI.AutoFillUiCallback client) {
-        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse 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 = createInlineSuggestion(requestId, dataset,
-                    fieldIndex,
-                    inlinePresentation, inlineSuggestionUi, client);
-            inlineSuggestions.add(inlineSuggestion);
-        }
-        return new InlineSuggestionsResponse(inlineSuggestions);
-    }
-
-    private static InlineSuggestion createAugmentedInlineSuggestion(@NonNull Dataset dataset,
-            @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
-        // TODO(b/146453195): fill in the autofill hint properly.
-        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
-                inlinePresentation.getInlinePresentationSpec(),
-                InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""},
-                InlineSuggestionInfo.TYPE_SUGGESTION);
-        final View.OnClickListener onClickListener = v -> {
-            inlineSuggestionUiCallback.autofill(dataset);
-        };
-        final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
-                        onClickListener));
-        return inlineSuggestion;
-    }
-
-    private static InlineSuggestion createInlineSuggestion(int requestId,
-            @NonNull Dataset dataset,
-            int fieldIndex,
-            @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @NonNull AutoFillUI.AutoFillUiCallback client) {
-        // TODO(b/146453195): fill in the autofill hint properly.
-        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
-                inlinePresentation.getInlinePresentationSpec(),
-                InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
-                InlineSuggestionInfo.TYPE_SUGGESTION);
-        final View.OnClickListener onClickListener = v -> {
-            client.fill(requestId, fieldIndex, dataset);
-        };
-        final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
-                        onClickListener));
-        return inlineSuggestion;
-    }
-
-    private static IInlineContentProvider.Stub createInlineContentProvider(
-            @NonNull InlinePresentation inlinePresentation,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @Nullable View.OnClickListener onClickListener) {
-        return new IInlineContentProvider.Stub() {
-            @Override
-            public void provideContent(int width, int height,
-                    IInlineContentCallback callback) {
-                UiThread.getHandler().post(() -> {
-                    SurfaceControl sc = inlineSuggestionUi.inflate(inlinePresentation, width,
-                            height,
-                            onClickListener);
-                    try {
-                        callback.onContent(sc);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Encounter exception calling back with inline content.");
-                    }
-                });
-            }
-        };
-    }
-
-    private InlineSuggestionFactory() {
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
new file mode 100644
index 0000000..1fc48d2
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Maintains an inline suggestion autofill session.
+ *
+ * <p> This class is thread safe.
+ */
+final class InlineSuggestionSession {
+
+    private static final String TAG = "InlineSuggestionSession";
+    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
+
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final int mUserId;
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final Object mLock;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private CompletableFuture<ImeResponse> mPendingImeResponse;
+
+    InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
+            int userId, ComponentName componentName) {
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mUserId = userId;
+        mComponentName = componentName;
+        mLock = new Object();
+    }
+
+    public void createRequest(@NonNull AutofillId currentViewId) {
+        synchronized (mLock) {
+            cancelCurrentRequest();
+            mPendingImeResponse = new CompletableFuture<>();
+            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+                    mUserId, mComponentName, currentViewId,
+                    new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse));
+        }
+    }
+
+    @Nullable
+    public ImeResponse waitAndGetImeResponse() {
+        CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
+        if (pendingImeResponse == null || pendingImeResponse.isCancelled()) {
+            return null;
+        }
+        try {
+            return pendingImeResponse.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+        } catch (CancellationException e) {
+            Log.w(TAG, "Inline suggestions request cancelled");
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    private void cancelCurrentRequest() {
+        CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
+        if (pendingImeResponse != null) {
+            pendingImeResponse.cancel(true);
+        }
+    }
+
+    @Nullable
+    @GuardedBy("mLock")
+    private CompletableFuture<ImeResponse> getPendingImeResponse() {
+        synchronized (mLock) {
+            return mPendingImeResponse;
+        }
+    }
+
+    private static final class InlineSuggestionsRequestCallbackImpl
+            extends IInlineSuggestionsRequestCallback.Stub {
+
+        private final CompletableFuture<ImeResponse> mResponse;
+
+        private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response) {
+            mResponse = response;
+        }
+
+        @Override
+        public void onInlineSuggestionsUnsupported() throws RemoteException {
+            if (sDebug) {
+                Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
+            }
+            mResponse.cancel(true);
+        }
+
+        @Override
+        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+                IInlineSuggestionsResponseCallback callback) throws RemoteException {
+            if (sDebug) {
+                Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
+            }
+            if (request != null && callback != null) {
+                mResponse.complete(new ImeResponse(request, callback));
+            } else {
+                mResponse.cancel(true);
+            }
+        }
+    }
+
+    /**
+     * A data class wrapping IME responses for the inline suggestion request.
+     */
+    public static class ImeResponse {
+        @NonNull
+        private final InlineSuggestionsRequest mRequest;
+
+        @NonNull
+        private final IInlineSuggestionsResponseCallback mCallback;
+
+        ImeResponse(@NonNull InlineSuggestionsRequest request,
+                @NonNull IInlineSuggestionsResponseCallback callback) {
+            mRequest = request;
+            mCallback = callback;
+        }
+
+        public InlineSuggestionsRequest getRequest() {
+            return mRequest;
+        }
+
+        public IInlineSuggestionsResponseCallback getCallback() {
+            return mCallback;
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5e6f6fea..d93ac68 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);
+                                            inlineSuggestionsRequest, inlineSuggestionsData,
+                                            focusedId, inlineSuggestionsCallback, client,
+                                            onErrorCallback);
                                     requestAutofill.complete(null);
                                 }
 
@@ -229,31 +231,35 @@
     }
 
     private void maybeRequestShowInlineSuggestions(int sessionId,
-            @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
+            @Nullable InlineSuggestionsRequest request, @Nullable Dataset[] inlineSuggestionsData,
+            @NonNull AutofillId focusedId,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) {
-        if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
+            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
+        if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null
+                || request == null) {
             return;
         }
         mCallbacks.setLastResponse(sessionId);
+
         try {
             inlineSuggestionsCallback.onInlineSuggestionsResponse(
                     InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
-                            inlineSuggestionsData, focusedId, mContext,
+                            request, 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 +281,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..53f85ea 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -100,9 +100,9 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ArrayUtils;
-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;
 
@@ -113,11 +113,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -158,9 +153,6 @@
     /** uid the session is for */
     public final int uid;
 
-    /** user id the session is for */
-    public final int userId;
-
     /** ID of the task associated with this session's activity */
     public final int taskId;
 
@@ -309,11 +301,8 @@
     @GuardedBy("mLock")
     private boolean mForAugmentedAutofillOnly;
 
-    @NonNull
-    private final InputMethodManagerInternal mInputMethodManagerInternal;
-
     @Nullable
-    private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback;
+    private final InlineSuggestionSession mInlineSuggestionSession;
 
     /**
      * Receiver of assist data from the app's {@link Activity}.
@@ -414,14 +403,16 @@
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
 
-                final InlineSuggestionsRequest suggestionsRequest =
-                        mInlineSuggestionsRequestCallback != null
-                                ? mInlineSuggestionsRequestCallback.getRequest() : null;
+                final InlineSuggestionSession.ImeResponse imeResponse =
+                        mInlineSuggestionSession.waitAndGetImeResponse();
 
                 request = new FillRequest(requestId, contexts, mClientState, flags,
-                        suggestionsRequest);
+                        imeResponse != null ? imeResponse.getRequest() : null);
             }
 
+            if (mActivityToken != null) {
+                mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData);
+            }
             mRemoteFillService.onFillRequest(request);
         }
 
@@ -614,75 +605,12 @@
     private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
             int newState, int flags) {
         if (isInlineSuggestionsEnabled()) {
-            mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
-            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId,
-                    mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
+            mInlineSuggestionSession.createRequest(mCurrentViewId);
         }
 
         requestNewFillResponseLocked(viewState, newState, flags);
     }
 
-    private static final class InlineSuggestionsRequestCallbackImpl
-            extends IInlineSuggestionsRequestCallback.Stub {
-        private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
-
-        private final CompletableFuture<InlineSuggestionsRequest> mRequest;
-        private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback;
-
-        private InlineSuggestionsRequestCallbackImpl() {
-            mRequest = new CompletableFuture<>();
-            mResponseCallback = new CompletableFuture<>();
-        }
-
-        @Override
-        public void onInlineSuggestionsUnsupported() throws RemoteException {
-            if (sDebug) {
-                Log.d(TAG, "inline suggestions request unsupported, "
-                        + "falling back to regular autofill");
-            }
-            mRequest.cancel(true);
-            mResponseCallback.cancel(true);
-        }
-
-        @Override
-        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
-                IInlineSuggestionsResponseCallback callback) throws RemoteException {
-            if (sDebug) {
-                Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
-            }
-            mRequest.complete(request);
-            mResponseCallback.complete(callback);
-        }
-
-        @Nullable
-        private InlineSuggestionsRequest getRequest() {
-            try {
-                return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions request cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-            return null;
-        }
-
-        @Nullable
-        private IInlineSuggestionsResponseCallback getResponseCallback() {
-            try {
-                return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions callback cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-            return null;
-        }
-    }
-
     /**
      * Reads a new structure and then request a new fill response from the fill service.
      */
@@ -762,7 +690,6 @@
         mFlags = flags;
         this.taskId = taskId;
         this.uid = uid;
-        this.userId = userId;
         mStartTime = SystemClock.elapsedRealtime();
         mService = service;
         mLock = lock;
@@ -781,7 +708,8 @@
         mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
         setClientLocked(client);
 
-        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
+                componentName);
 
         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
@@ -2673,15 +2601,16 @@
         }
 
         if (response.supportsInlineSuggestions()) {
-            if (requestShowInlineSuggestions(response)) {
-                //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, rather
-                // than here where framework sends back the response.
-                mService.logDatasetShown(id, mClientState);
-                return;
+            synchronized (mLock) {
+                if (requestShowInlineSuggestionsLocked(response)) {
+                    //TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
+                    // rather than here where framework sends back the response.
+                    mService.logDatasetShown(id, mClientState);
+                    return;
+                }
             }
         }
 
-
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2716,26 +2645,32 @@
     /**
      * Returns whether we made a request to show inline suggestions.
      */
-    private boolean requestShowInlineSuggestions(FillResponse response) {
-        final IInlineSuggestionsResponseCallback inlineContentCallback =
-                mInlineSuggestionsRequestCallback != null
-                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
-        if (inlineContentCallback == null) {
-            Log.w(TAG, "Session input method callback is not set yet");
-            return false;
-        }
-
+    private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response) {
         final List<Dataset> datasets = response.getDatasets();
         if (datasets == null) {
             Log.w(TAG, "response returned null datasets");
             return false;
         }
 
+        final InlineSuggestionSession.ImeResponse imeResponse =
+                mInlineSuggestionSession.waitAndGetImeResponse();
+        if (imeResponse == null) {
+            Log.w(TAG, "Session input method callback is not set yet");
+            return false;
+        }
+
+        final InlineSuggestionsRequest request = imeResponse.getRequest();
         InlineSuggestionsResponse inlineSuggestionsResponse =
-                InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(),
-                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this);
-        try  {
-            inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
+                InlineSuggestionFactory.createInlineSuggestionsResponse(request,
+                        response.getRequestId(),
+                        datasets.toArray(new Dataset[]{}), response.getInlineActions(),
+                        mCurrentViewId, mContext, this, () -> {
+                            synchronized (mLock) {
+                                requestHideFillUi(mCurrentViewId);
+                            }
+                        });
+        try {
+            imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse);
         } catch (RemoteException e) {
             Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
             return false;
@@ -3017,14 +2952,27 @@
 
         final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
 
+        // There are 3 cases when augmented autofill should ask IME for a new request:
+        // 1. standard autofill provider is None
+        // 2. standard autofill provider doesn't support inline (and returns null response)
+        // 3. standard autofill provider supports inline, but isn't called because the field
+        // doesn't want autofill
+        if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabled()) {
+            if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
+            mInlineSuggestionSession.createRequest(mCurrentViewId);
+        }
+        InlineSuggestionSession.ImeResponse imeResponse =
+                mInlineSuggestionSession.waitAndGetImeResponse();
         final InlineSuggestionsRequest inlineSuggestionsRequest =
-                mInlineSuggestionsRequestCallback != null
-                        ? mInlineSuggestionsRequestCallback.getRequest() : null;
+                imeResponse != null ? imeResponse.getRequest() : null;
         final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
-                mInlineSuggestionsRequestCallback != null
-                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
+                imeResponse != null ? imeResponse.getCallback() : 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/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
new file mode 100644
index 0000000..95a4a19
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -0,0 +1,216 @@
+/*
+ * 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 static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.RemoteException;
+import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.inline.InlinePresentationSpec;
+import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+import android.widget.Toast;
+
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.server.UiThread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public final class InlineSuggestionFactory {
+    private static final String TAG = "InlineSuggestionFactory";
+
+    /**
+     * Callback from the inline suggestion Ui.
+     */
+    public interface InlineSuggestionUiCallback {
+        /**
+         * Callback to autofill a dataset to the client app.
+         */
+        void autofill(@NonNull Dataset dataset);
+    }
+
+    /**
+     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by
+     * augmented autofill service.
+     */
+    public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
+            @NonNull InlineSuggestionsRequest request,
+            @NonNull Dataset[] datasets,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
+            @NonNull Runnable onErrorCallback) {
+        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
+        return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
+                datasets, /* inlineActions= */ null, autofillId, context, onErrorCallback,
+                (dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset)));
+    }
+
+    /**
+     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
+     * autofill service.
+     */
+    public static InlineSuggestionsResponse createInlineSuggestionsResponse(
+            @NonNull InlineSuggestionsRequest request, int requestId,
+            @NonNull Dataset[] datasets,
+            @Nullable List<InlinePresentation> inlineActions,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull AutoFillUI.AutoFillUiCallback client,
+            @NonNull Runnable onErrorCallback) {
+        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
+        return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets,
+                inlineActions, autofillId, context, onErrorCallback,
+                (dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset)));
+    }
+
+    private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
+            boolean isAugmented, @NonNull InlineSuggestionsRequest request,
+            @NonNull Dataset[] datasets, @Nullable List<InlinePresentation> inlineActions,
+            @NonNull AutofillId autofillId, @NonNull Context context,
+            @NonNull Runnable onErrorCallback,
+            @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
+                onErrorCallback);
+        for (int i = 0; i < datasets.length; i++) {
+            final Dataset dataset = datasets[i];
+            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 = createInlineSuggestion(isAugmented, dataset,
+                    fieldIndex, mergedInlinePresentation(request, i, inlinePresentation),
+                    inlineSuggestionUi, onClickListenerFactory);
+            inlineSuggestions.add(inlineSuggestion);
+        }
+        if (inlineActions != null) {
+            for (InlinePresentation inlinePresentation : inlineActions) {
+                final InlineSuggestion inlineAction = createInlineAction(isAugmented, context,
+                        mergedInlinePresentation(request, 0, inlinePresentation),
+                        inlineSuggestionUi);
+                inlineSuggestions.add(inlineAction);
+            }
+        }
+        return new InlineSuggestionsResponse(inlineSuggestions);
+    }
+
+    private static InlineSuggestion createInlineAction(boolean isAugmented,
+            @NonNull Context context,
+            @NonNull InlinePresentation inlinePresentation,
+            @NonNull InlineSuggestionUi inlineSuggestionUi) {
+        // TODO(b/146453195): fill in the autofill hint properly.
+        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+                inlinePresentation.getInlinePresentationSpec(),
+                isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
+                        : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
+                InlineSuggestionInfo.TYPE_ACTION);
+        final View.OnClickListener onClickListener = v -> {
+            // TODO(b/148567875): Launch the intent provided through the slice. This
+            //  should be part of the UI renderer therefore will be moved to the support
+            //  library.
+            Toast.makeText(context, "icon clicked", Toast.LENGTH_SHORT).show();
+        };
+        return new InlineSuggestion(inlineSuggestionInfo,
+                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
+                        onClickListener));
+    }
+
+    private static InlineSuggestion createInlineSuggestion(boolean isAugmented,
+            @NonNull Dataset dataset,
+            int fieldIndex, @NonNull InlinePresentation inlinePresentation,
+            @NonNull InlineSuggestionUi inlineSuggestionUi,
+            @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+        // TODO(b/146453195): fill in the autofill hint properly.
+        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+                inlinePresentation.getInlinePresentationSpec(),
+                isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
+                        : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
+                InlineSuggestionInfo.TYPE_SUGGESTION);
+        final View.OnClickListener onClickListener = onClickListenerFactory.apply(dataset,
+                fieldIndex);
+        final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
+                createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
+                        onClickListener));
+        return inlineSuggestion;
+    }
+
+    /**
+     * Returns an {@link InlinePresentation} with the style spec from the request/host, and
+     * everything else from the provided {@code inlinePresentation}.
+     */
+    private static InlinePresentation mergedInlinePresentation(
+            @NonNull InlineSuggestionsRequest request,
+            int index, @NonNull InlinePresentation inlinePresentation) {
+        final List<InlinePresentationSpec> specs = request.getPresentationSpecs();
+        if (specs.isEmpty()) {
+            return inlinePresentation;
+        }
+        InlinePresentationSpec specFromHost = specs.get(Math.min(specs.size() - 1, index));
+        InlinePresentationSpec mergedInlinePresentation = new InlinePresentationSpec.Builder(
+                inlinePresentation.getInlinePresentationSpec().getMinSize(),
+                inlinePresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
+                specFromHost.getStyle()).build();
+        return new InlinePresentation(inlinePresentation.getSlice(), mergedInlinePresentation,
+                inlinePresentation.isPinned());
+    }
+
+    private static IInlineContentProvider.Stub createInlineContentProvider(
+            @NonNull InlinePresentation inlinePresentation,
+            @NonNull InlineSuggestionUi inlineSuggestionUi,
+            @Nullable View.OnClickListener onClickListener) {
+        return new IInlineContentProvider.Stub() {
+            @Override
+            public void provideContent(int width, int height,
+                    IInlineContentCallback callback) {
+                UiThread.getHandler().post(() -> {
+                    SurfaceControl sc = inlineSuggestionUi.inflate(inlinePresentation, width,
+                            height,
+                            onClickListener);
+                    try {
+                        callback.onContent(sc);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Encounter exception calling back with inline content.");
+                    }
+                });
+            }
+        };
+    }
+
+    private InlineSuggestionFactory() {
+    }
+}
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..e813dae
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
@@ -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.
+ */
+
+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();
+        setFocusable(false);
+    }
+
+    @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/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 583c5b5..32bca35 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -426,18 +426,26 @@
     public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
             @NonNull Bundle data) {
         final int id = getSessionId(activityToken);
+        final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
+        final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
+        final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
+        final SnapshotData snapshotData = new SnapshotData(assistData,
+                assistStructure, assistContent);
         if (id != NO_SESSION_ID) {
             final ContentCaptureServerSession session = mSessions.get(id);
-            final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
-            final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
-            final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
-            final SnapshotData snapshotData = new SnapshotData(assistData,
-                    assistStructure, assistContent);
             session.sendActivitySnapshotLocked(snapshotData);
             return true;
-        } else {
-            Slog.e(TAG, "Failed to notify activity assist data for activity: " + activityToken);
         }
+
+        // We want to send an activity snapshot regardless of whether a content capture session is
+        // present or not since a content capture session is not required for this functionality
+        if (mRemoteService != null) {
+            mRemoteService.onActivitySnapshotRequest(NO_SESSION_ID, snapshotData);
+            Slog.d(TAG, "Notified activity assist data for activity: "
+                    + activityToken + " without a session Id");
+            return true;
+        }
+
         return false;
     }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a603fa9..6fc6084 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -98,7 +98,7 @@
         "android.hardware.tv.cec-V1.0-java",
         "android.hardware.vibrator-java",
         "app-compat-annotations",
-        "framework-tethering",
+        "framework-tethering-stubs",
     ],
 
     required: [
@@ -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 d0cd7ff..687fb7d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -66,6 +66,7 @@
 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;
@@ -601,7 +602,7 @@
 
     private Set<String> mWolSupportedInterfaces;
 
-    private TelephonyManager mTelephonyManager;
+    private final TelephonyManager mTelephonyManager;
     private final AppOpsManager mAppOpsManager;
 
     private final LocationPermissionChecker mLocationPermissionChecker;
@@ -960,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);
@@ -988,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");
@@ -1168,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);
         }
@@ -1698,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.
@@ -3016,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;
         }
@@ -3104,7 +3123,13 @@
         handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
     }
 
-    private void updateLingerState(NetworkAgentInfo nai, long now) {
+    /**
+     * Updates the linger state from the network requests inside the NAI.
+     * @param nai the agent info to update
+     * @param now the timestamp of the event causing this update
+     * @return whether the network was lingered as a result of this update
+     */
+    private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) {
         // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
         // 2. If the network was lingering and there are now requests, unlinger it.
         // 3. If this network is unneeded (which implies it is not lingering), and there is at least
@@ -3115,12 +3140,15 @@
             nai.unlinger();
             logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
         } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) {
-            int lingerTime = (int) (nai.getLingerExpiry() - now);
-            if (DBG) log("Lingering " + nai.name() + " for " + lingerTime + "ms");
+            if (DBG) {
+                final int lingerTime = (int) (nai.getLingerExpiry() - now);
+                log("Lingering " + nai.name() + " for " + lingerTime + "ms");
+            }
             nai.linger();
             logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
-            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+            return true;
         }
+        return false;
     }
 
     private void handleAsyncChannelHalfConnect(Message msg) {
@@ -3464,7 +3492,10 @@
                 }
                 // If there are still lingered requests on this network, don't tear it down,
                 // but resume lingering instead.
-                updateLingerState(nai, SystemClock.elapsedRealtime());
+                final long now = SystemClock.elapsedRealtime();
+                if (updateLingerState(nai, now)) {
+                    notifyNetworkLosing(nai, now);
+                }
                 if (unneeded(nai, UnneededFor.TEARDOWN)) {
                     if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
                     teardownUnneededNetwork(nai);
@@ -4189,6 +4220,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(
@@ -5275,7 +5319,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");
         }
@@ -5285,6 +5329,7 @@
             throw new SecurityException(
                     "Insufficient permissions to request a specific signal strength");
         }
+        mAppOpsManager.checkPackage(callerUid, callerPackageName);
     }
 
     private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
@@ -5331,7 +5376,6 @@
             return;
         }
         MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
-        ns.assertValidFromUid(Binder.getCallingUid());
     }
 
     private void ensureValid(NetworkCapabilities nc) {
@@ -5343,7 +5387,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;
@@ -5351,7 +5397,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);
@@ -5363,13 +5409,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");
@@ -5444,16 +5491,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);
@@ -5501,15 +5550,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.
@@ -5529,17 +5579,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);
@@ -6552,6 +6602,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<>();
@@ -6562,7 +6613,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;
 
@@ -6578,11 +6632,16 @@
                 }
                 if (currentNetwork == null || currentNetwork.getCurrentScore() < score) {
                     reassignedRequests.put(nri, newNetwork);
+                    changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
+                            nri, currentNetwork, newNetwork));
                 }
             } else if (newNetwork == currentNetwork) {
                 reassignedRequests.put(nri, null);
+                changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
+                        nri, currentNetwork, null));
             }
         }
+
         return reassignedRequests;
     }
 
@@ -6612,7 +6671,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.
@@ -6621,41 +6680,39 @@
             final NetworkRequestInfo nri = entry.getKey();
             final NetworkAgentInfo previousSatisfier = nri.mSatisfier;
             final NetworkAgentInfo newSatisfier = entry.getValue();
-            changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
-                    nri, previousSatisfier, newSatisfier));
-            if (newSatisfier != null) {
-                if (VDBG) log("rematch for " + newSatisfier.name());
-                if (previousSatisfier != null) {
-                    if (VDBG || DDBG) {
-                        log("   accepting network in place of " + previousSatisfier.name());
-                    }
-                    previousSatisfier.removeRequest(nri.request.requestId);
-                    previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
-                } else {
-                    if (VDBG || DDBG) log("   accepting network in place of null");
-                }
-                newSatisfier.unlingerRequest(nri.request);
-                if (!newSatisfier.addRequest(nri.request)) {
-                    Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
-                }
-            } else {
-                // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",
-                // mark it as no longer satisfying "nri".  Because networks are processed by
-                // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will
-                // match "newNetwork" before this loop will encounter a "currentNetwork" with higher
-                // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".
-                // This means this code doesn't have to handle the case where "currentNetwork" no
-                // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork".
-                if (DBG) {
-                    log("Network " + newNetwork.name() + " stopped satisfying" +
-                            " request " + nri.request.requestId);
-                }
-                newNetwork.removeRequest(nri.request.requestId);
-            }
-            nri.mSatisfier = newSatisfier;
+            updateSatisfiersForRematchRequest(nri, previousSatisfier, newSatisfier, now);
         }
     }
 
+    private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
+            @Nullable final NetworkAgentInfo previousSatisfier,
+            @Nullable final NetworkAgentInfo newSatisfier,
+            final long now) {
+        if (newSatisfier != null) {
+            if (VDBG) log("rematch for " + newSatisfier.name());
+            if (previousSatisfier != null) {
+                if (VDBG || DDBG) {
+                    log("   accepting network in place of " + previousSatisfier.name());
+                }
+                previousSatisfier.removeRequest(nri.request.requestId);
+                previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
+            } else {
+                if (VDBG || DDBG) log("   accepting network in place of null");
+            }
+            newSatisfier.unlingerRequest(nri.request);
+            if (!newSatisfier.addRequest(nri.request)) {
+                Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
+            }
+        } else {
+            if (DBG) {
+                log("Network " + previousSatisfier.name() + " stopped satisfying"
+                        + " request " + nri.request.requestId);
+            }
+            previousSatisfier.removeRequest(nri.request.requestId);
+        }
+        nri.mSatisfier = newSatisfier;
+    }
+
     /**
      * Attempt to rematch all Networks with NetworkRequests.  This may result in Networks
      * being disconnected.
@@ -6731,13 +6788,20 @@
             processNewlySatisfiedListenRequests(event.mNetwork);
         }
 
+        final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>();
         for (final NetworkAgentInfo nai : nais) {
             // Rematching may have altered the linger state of some networks, so update all linger
             // timers. updateLingerState reads the state from the network agent and does nothing
             // if the state has not changed : the source of truth is controlled with
             // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been
             // called while rematching the individual networks above.
-            updateLingerState(nai, now);
+            if (updateLingerState(nai, now)) {
+                lingeredNetworks.add(nai);
+            }
+        }
+
+        for (final NetworkAgentInfo nai : lingeredNetworks) {
+            notifyNetworkLosing(nai, now);
         }
 
         updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
@@ -6753,7 +6817,9 @@
                     // and became unneeded due to another network improving its score to the
                     // point where this network will no longer be able to satisfy any requests
                     // even if it validates.
-                    updateLingerState(nai, now);
+                    if (updateLingerState(nai, now)) {
+                        notifyNetworkLosing(nai, now);
+                    }
                 } else {
                     if (DBG) log("Reaping " + nai.name());
                     teardownUnneededNetwork(nai);
@@ -7006,6 +7072,12 @@
         callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
     }
 
+    // Notify the requests on this NAI that the network is now lingered.
+    private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
+        final int lingerTime = (int) (nai.getLingerExpiry() - now);
+        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+    }
+
     /**
      * Notify of the blocked state apps with a registered callback matching a given NAI.
      *
@@ -7519,6 +7591,8 @@
      */
     @VisibleForTesting
     class ConnectivityDiagnosticsHandler extends Handler {
+        private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName();
+
         /**
          * Used to handle ConnectivityDiagnosticsCallback registration events from {@link
          * android.net.ConnectivityDiagnosticsManager}.
@@ -7546,6 +7620,26 @@
          */
         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);
         }
@@ -7576,6 +7670,24 @@
                     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);
+                }
             }
         }
     }
@@ -7705,6 +7817,35 @@
         }
     }
 
+    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<>();
@@ -7757,12 +7898,13 @@
             throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
                     + " Please use NetworkCapabilities instead.");
         }
-        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName);
+        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(
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/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 135f6f3..6fb7b26 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -272,10 +272,11 @@
     private void handlePinOnStart() {
         final String bootImage = SystemProperties.get("dalvik.vm.boot-image", "");
         String[] filesToPin = null;
-        if (bootImage.endsWith("apex.art")) {
-            // Use the files listed for that specific boot image
+        if (bootImage.endsWith("boot-image.prof")) {
+            // Use the files listed for that specific boot image.
+            // TODO: find a better way to know we're using the JIT zygote configuration.
             filesToPin = mContext.getResources().getStringArray(
-                  com.android.internal.R.array.config_apexBootImagePinnerServiceFiles);
+                  com.android.internal.R.array.config_jitzygoteBootImagePinnerServiceFiles);
         } else {
             // Files to pin come from the overlay and can be specified per-device config
             filesToPin = mContext.getResources().getStringArray(
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index cbf6c27..0e5a6bb 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -284,17 +284,17 @@
     static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
                         | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
-                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+                        | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+
+    static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
+                PhoneStateListener.LISTEN_PRECISE_CALL_STATE
+                        | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
+                        | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
+                        | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED
+                        | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES
                         | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
                         | PhoneStateListener.LISTEN_BARRING_INFO;
 
-    static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
-                PhoneStateListener.LISTEN_PRECISE_CALL_STATE
-                | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
-                | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
-                | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED
-                | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES;
-
     static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
             PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
                     | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS;
@@ -2535,7 +2535,7 @@
             }
         }
 
-        if ((events & PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
+        if ((events & ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
             // check if calling app has either permission READ_PRECISE_PHONE_STATE
             // or with carrier privileges
             try {
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..0686e3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -466,18 +466,9 @@
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10*1000;
-    // How long we wait for an attached process to publish its content providers
-    // before we decide it must be hung.
-    static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
-
     // How long we wait to kill an application zygote, after the last process using
     // it has gone away.
     static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
-    /**
-     * How long we wait for an provider to be published. Should be longer than
-     * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}.
-     */
-    static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000;
 
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real, when the process was
@@ -2617,7 +2608,7 @@
         mProcessCpuThread.start();
 
         mBatteryStatsService.publish();
-        mAppOpsService.publish(mContext);
+        mAppOpsService.publish();
         Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, mInternal);
         mActivityTaskManager.onActivityManagerInternalAdded();
@@ -4934,7 +4925,8 @@
         if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
             Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
             msg.obj = app;
-            mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
+            mHandler.sendMessageDelayed(msg,
+                    ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);
         }
 
         checkTime(startTime, "attachApplicationLocked: before bindApplication");
@@ -6843,7 +6835,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 +6924,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 +6997,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;
                 }
@@ -7198,7 +7193,8 @@
         }
 
         // Wait for the provider to be published...
-        final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
+        final long timeout =
+                SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS;
         boolean timedOut = false;
         synchronized (cpr) {
             while (cpr.provider == null) {
@@ -7235,12 +7231,14 @@
             }
         }
         if (timedOut) {
-            // Note we do it afer releasing the lock.
+            // Note we do it after releasing the lock.
             String callerName = "unknown";
-            synchronized (this) {
-                final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
-                if (record != null) {
-                    callerName = record.processName;
+            if (caller != null) {
+                synchronized (this) {
+                    final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
+                    if (record != null) {
+                        callerName = record.processName;
+                    }
                 }
             }
 
@@ -11068,10 +11066,12 @@
         }
 
         if (dumpAll || dumpPackage != null) {
+            final SparseArray<ProcessRecord> pidToProcess = new SparseArray<>();
             synchronized (mPidsSelfLocked) {
                 boolean printed = false;
                 for (int i=0; i<mPidsSelfLocked.size(); i++) {
                     ProcessRecord r = mPidsSelfLocked.valueAt(i);
+                    pidToProcess.put(r.pid, r);
                     if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                         continue;
                     }
@@ -11085,6 +11085,32 @@
                         pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
                 }
             }
+
+            synchronized (sActiveProcessInfoSelfLocked) {
+                boolean printed = false;
+                for (int i = 0; i < sActiveProcessInfoSelfLocked.size(); i++) {
+                    ProcessInfo info = sActiveProcessInfoSelfLocked.valueAt(i);
+                    ProcessRecord r = pidToProcess.get(sActiveProcessInfoSelfLocked.keyAt(i));
+                    if (r != null && dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  Active process infos:");
+                        printed = true;
+                    }
+                    pw.print("    Pinfo PID #"); pw.print(sActiveProcessInfoSelfLocked.keyAt(i));
+                    pw.println(":");
+                    pw.print("      name="); pw.println(info.name);
+                    if (info.deniedPermissions != null) {
+                        for (int j = 0; j < info.deniedPermissions.size(); j++) {
+                            pw.print("      deny: ");
+                            pw.println(info.deniedPermissions.valueAt(i));
+                        }
+                    }
+                }
+            }
         }
 
         if (mImportantProcesses.size() > 0) {
@@ -19507,7 +19533,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..eec68dc 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();
         }
@@ -946,6 +946,15 @@
                 }
 
                 EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
+
+                // See above for why we're not taking mPhenotypeFlagLock here
+                if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
+                            FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
+                            pid,
+                            name,
+                            unfrozenDuration);
+                }
             }
         }
 
@@ -994,6 +1003,16 @@
                 }
 
                 EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, name);
+
+                // See above for why we're not taking mPhenotypeFlagLock here
+                if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+                    FrameworkStatsLog.write(
+                            FrameworkStatsLog.APP_FREEZE_CHANGED,
+                            FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
+                            pid,
+                            name,
+                            frozenDuration);
+                }
             }
         }
     }
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/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2b4d15e..f3d8bc8 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -371,6 +371,15 @@
             }
         }
         pw.println("}");
+        if (processInfo != null) {
+            pw.print(prefix); pw.println("processInfo:");
+            if (processInfo.deniedPermissions != null) {
+                for (int i = 0; i < processInfo.deniedPermissions.size(); i++) {
+                    pw.print(prefix); pw.print("  deny: ");
+                    pw.println(processInfo.deniedPermissions.valueAt(i));
+                }
+            }
+        }
         pw.print(prefix); pw.print("mRequiredAbi="); pw.print(mRequiredAbi);
                 pw.print(" instructionSet="); pw.println(instructionSet);
         if (info.className != null) {
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/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 60f420e..e17c1f8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -24,7 +24,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioManager;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -402,7 +402,7 @@
     }
 
     /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAddress device) {
+                                                      @NonNull AudioDevice device) {
         return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device);
     }
 
@@ -543,7 +543,7 @@
         sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
-    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device)
+    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDevice device)
     {
         sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
     }
@@ -904,7 +904,7 @@
                 } break;
                 case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: {
                     final int strategy = msg.arg1;
-                    final AudioDeviceAddress device = (AudioDeviceAddress) msg.obj;
+                    final AudioDevice device = (AudioDevice) msg.obj;
                     mDeviceInventory.onSaveSetPreferredDevice(strategy, device);
                 } break;
                 case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 75d9dd8..1f998c3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -23,7 +23,7 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioDevicePort;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -75,7 +75,7 @@
     private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
 
     // List of preferred devices for strategies
-    private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
+    private final ArrayMap<Integer, AudioDevice> mPreferredDevices = new ArrayMap<>();
 
     // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
     private final @NonNull AudioSystemAdapter mAudioSystem;
@@ -468,7 +468,7 @@
         }
     }
 
-    /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAddress device) {
+    /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDevice device) {
         mPreferredDevices.put(strategy, device);
     }
 
@@ -480,7 +480,7 @@
     //
 
     /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAddress device) {
+                                                      @NonNull AudioDevice device) {
         final long identity = Binder.clearCallingIdentity();
         final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device);
         Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2548af..342ce22 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -65,7 +65,7 @@
 import android.hidl.manager.V1_0.IServiceManager;
 import android.media.AudioAttributes;
 import android.media.AudioAttributes.AttributeSystemUsage;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
@@ -1712,7 +1712,7 @@
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
     /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */
-    public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device) {
+    public int setPreferredDeviceForStrategy(int strategy, AudioDevice device) {
         if (device == null) {
             return AudioSystem.ERROR;
         }
@@ -1721,7 +1721,7 @@
                 "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
         sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
-        if (device.getRole() == AudioDeviceAddress.ROLE_INPUT) {
+        if (device.getRole() == AudioDevice.ROLE_INPUT) {
             Log.e(TAG, "Unsupported input routing in " + logString);
             return AudioSystem.ERROR;
         }
@@ -1749,9 +1749,9 @@
     }
 
     /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */
-    public AudioDeviceAddress getPreferredDeviceForStrategy(int strategy) {
+    public AudioDevice getPreferredDeviceForStrategy(int strategy) {
         enforceModifyAudioRoutingPermission();
-        AudioDeviceAddress[] devices = new AudioDeviceAddress[1];
+        AudioDevice[] devices = new AudioDevice[1];
         final long identity = Binder.clearCallingIdentity();
         final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices);
         Binder.restoreCallingIdentity(identity);
@@ -1765,7 +1765,7 @@
     }
 
     /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
-    public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+    public @NonNull ArrayList<AudioDevice> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         Objects.requireNonNull(attributes);
         enforceModifyAudioRoutingPermission();
@@ -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/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 9d06b42..a3086c0 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -17,7 +17,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
-import android.media.AudioDeviceAddress;
+import android.media.AudioDevice;
 import android.media.AudioSystem;
 import android.util.Log;
 
@@ -86,12 +86,12 @@
     }
 
     /**
-     * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDeviceAddress)}
+     * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDevice)}
      * @param strategy
      * @param device
      * @return
      */
-    public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) {
+    public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDevice device) {
         return AudioSystem.setPreferredDeviceForStrategy(strategy, device);
     }
 
@@ -138,7 +138,7 @@
         }
 
         @Override
-        public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) {
+        public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDevice device) {
             return AudioSystem.AUDIO_STATUS_OK;
         }
 
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 704cbff4..fc9542a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -65,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;
@@ -133,14 +134,9 @@
                         hdrCapabilities, isInternal);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
-            } else {
-                boolean changed = device.updateDisplayConfigsLocked(configs, activeConfig,
-                        configSpecs);
-                changed |= device.updateColorModesLocked(colorModes, activeColorMode);
-                changed |= device.updateHdrCapabilitiesLocked(hdrCapabilities);
-                if (changed) {
-                    sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
-                }
+            } else if (device.updateDisplayProperties(configs, activeConfig,
+                    configSpecs, colorModes, activeColorMode, hdrCapabilities)) {
+                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
         } else {
             // The display is no longer available. Ignore the attempt to add it.
@@ -215,10 +211,8 @@
             mPhysicalDisplayId = physicalDisplayId;
             mIsInternal = isInternal;
             mDisplayInfo = info;
-
-            updateDisplayConfigsLocked(configs, activeConfigId, configSpecs);
-            updateColorModesLocked(colorModes, activeColorMode);
-            updateHdrCapabilitiesLocked(hdrCapabilities);
+            updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes,
+                    activeColorMode, hdrCapabilities);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             if (mIsInternal) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
@@ -240,13 +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) {
             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];
@@ -286,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();
@@ -294,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
@@ -323,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.");
@@ -345,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.");
                 }
@@ -797,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);
@@ -922,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/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index 2992f1e..a7e1a28 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -291,7 +291,7 @@
                 mWfdEnabling = true;
 
                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
-                wfdInfo.setWfdEnabled(true);
+                wfdInfo.setEnabled(true);
                 wfdInfo.setDeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
                 wfdInfo.setSessionAvailable(true);
                 wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
@@ -323,7 +323,7 @@
             // WFD should be disabled.
             if (mWfdEnabled || mWfdEnabling) {
                 WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
-                wfdInfo.setWfdEnabled(false);
+                wfdInfo.setEnabled(false);
                 mWifiP2pManager.setWfdInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
                     @Override
                     public void onSuccess() {
@@ -1044,7 +1044,7 @@
     private static boolean isWifiDisplay(WifiP2pDevice device) {
         WifiP2pWfdInfo wfdInfo = device.getWfdInfo();
         return wfdInfo != null
-                && wfdInfo.isWfdEnabled()
+                && wfdInfo.isEnabled()
                 && isPrimarySinkDeviceType(wfdInfo.getDeviceType());
     }
 
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..68ced79 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,19 @@
                 return;
             }
 
-            String appCert = getCertificateFingerprint(packageInfo);
+            List<String> appCertificates = getCertificateFingerprint(packageInfo);
+            List<String> installerCertificates =
+                    getInstallerCertificateFingerprint(installerPackageName);
+
+            Slog.w(TAG, appCertificates.toString());
 
             AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
 
             builder.setPackageName(getPackageNameNormalized(packageName));
-            builder.setAppCertificate(appCert == null ? "" : appCert);
+            builder.setAppCertificates(appCertificates);
             builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
-            builder.setInstallerCertificate(
-                    getInstallerCertificateFingerprint(installerPackageName));
+            builder.setInstallerCertificates(installerCertificates);
             builder.setIsPreInstalled(isSystemApp(packageName));
 
             AppInstallMetadata appInstallMetadata = builder.build();
@@ -290,7 +301,7 @@
             FrameworkStatsLog.write(
                     FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED,
                     packageName,
-                    appCert,
+                    appCertificates.toString(),
                     appInstallMetadata.getVersionCode(),
                     installerPackageName,
                     result.getLoggingResponse(),
@@ -320,7 +331,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 +410,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 +460,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 +507,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 +527,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/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index 87eee4e..60e8cce 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -63,10 +63,12 @@
                 searchIndexingKeysRangeContainingKey(
                         sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
 
-        // Add the range for app certificate indexes rules.
-        indexRanges.add(
-                searchIndexingKeysRangeContainingKey(
-                        sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate()));
+        // Add the range for app certificate indexes rules of all certificates.
+        for (String appCertificate : appInstallMetadata.getAppCertificates()) {
+            indexRanges.add(
+                    searchIndexingKeysRangeContainingKey(
+                            sAppCertificateBasedIndexes, appCertificate));
+        }
 
         // Add the range for unindexed rules.
         indexRanges.add(
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 669f1ac..6474303 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -55,6 +56,7 @@
     private final Context mContext;
     private final BluetoothAdapter mBluetoothAdapter;
     private final BluetoothRoutesUpdatedListener mListener;
+    private final AudioManager mAudioManager;
     private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
     private final IntentFilter mIntentFilter = new IntentFilter();
     private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
@@ -82,6 +84,7 @@
         mContext = context;
         mBluetoothAdapter = btAdapter;
         mListener = listener;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         buildBluetoothRoutes();
 
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
@@ -181,12 +184,15 @@
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
         BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
         newBtRoute.btDevice = device;
+        // Current / Max volume will be set when connected.
+        // TODO: Is there any BT device which has fixed volume?
         newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
                 .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
                 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
                 .setDescription(mContext.getResources().getText(
                         R.string.bluetooth_a2dp_audio_route_name).toString())
                 .setDeviceType(MediaRoute2Info.DEVICE_TYPE_BLUETOOTH)
+                .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .build();
         newBtRoute.connectedProfiles = new SparseBooleanArray();
         return newBtRoute;
@@ -203,10 +209,20 @@
             Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
             return;
         }
-        if (btRoute.route.getConnectionState() != state) {
-            btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
-                    .setConnectionState(state).build();
+        if (btRoute.route.getConnectionState() == state) {
+            return;
         }
+
+        // Update volume when the connection state is changed.
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
+                .setConnectionState(state);
+
+        if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
+            int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+            int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            builder.setVolumeMax(maxVolume).setVolume(currentVolume);
+        }
+        btRoute.route = builder.build();
     }
 
     interface BluetoothRoutesUpdatedListener {
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
new file mode 100644
index 0000000..58c2707
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -0,0 +1,360 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Holds the media button receiver, and also provides helper methods around it.
+ */
+final class MediaButtonReceiverHolder {
+    public static final int COMPONENT_TYPE_INVALID = 0;
+    public static final int COMPONENT_TYPE_BROADCAST = 1;
+    public static final int COMPONENT_TYPE_ACTIVITY = 2;
+    public static final int COMPONENT_TYPE_SERVICE = 3;
+
+    @IntDef(value = {
+            COMPONENT_TYPE_INVALID,
+            COMPONENT_TYPE_BROADCAST,
+            COMPONENT_TYPE_ACTIVITY,
+            COMPONENT_TYPE_SERVICE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ComponentType {}
+
+    private static final String TAG = "PendingIntentHolder";
+    private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT;
+    private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
+
+    private final int mUserId;
+    private final PendingIntent mPendingIntent;
+    private final ComponentName mComponentName;
+    private final String mPackageName;
+    @ComponentType
+    private final int mComponentType;
+
+    /**
+     * Unflatten from string which is previously flattened string via flattenToString().
+     * <p>
+     * It's used to store and restore media button receiver across the boot, by keeping the intent's
+     * component name to the persistent storage.
+     *
+     * @param mediaButtonReceiverInfo previously flattened string via flattenToString()
+     * @return new instance if the string was valid. {@code null} otherwise.
+     */
+    public static MediaButtonReceiverHolder unflattenFromString(
+            Context context, String mediaButtonReceiverInfo) {
+        if (TextUtils.isEmpty(mediaButtonReceiverInfo)) {
+            return null;
+        }
+        String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
+        if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
+            return null;
+        }
+        ComponentName componentName = ComponentName.unflattenFromString(tokens[0]);
+        int userId = Integer.parseInt(tokens[1]);
+        // Guess component type if the OS version is updated from the older version.
+        int componentType = (tokens.length == 3)
+                ?  Integer.parseInt(tokens[2])
+                : getComponentType(context, componentName);
+        return new MediaButtonReceiverHolder(userId, null, componentName, componentType);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param context context
+     * @param userId userId
+     * @param pendingIntent pending intent
+     * @return Can be {@code null} if pending intent was null.
+     */
+    public static MediaButtonReceiverHolder create(Context context, int userId,
+            PendingIntent pendingIntent) {
+        if (pendingIntent == null) {
+            return null;
+        }
+        ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null)
+                ? pendingIntent.getIntent().getComponent() : null;
+        if (componentName != null) {
+            // Explicit intent, where component name is in the PendingIntent.
+            return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
+                    getComponentType(context, componentName));
+        }
+
+        // Implicit intent, where component name isn't in the PendingIntent. Try resolve.
+        PackageManager pm = context.getPackageManager();
+        Intent intent = pendingIntent.getIntent();
+        if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) {
+            return new MediaButtonReceiverHolder(
+                    userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE);
+        } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent))
+                != null) {
+            return new MediaButtonReceiverHolder(
+                    userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST);
+        } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) {
+            return new MediaButtonReceiverHolder(
+                    userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY);
+        }
+
+        // Failed to resolve target component for the pending intent. It's unlikely to be usable.
+        // However, the pending intent would be still used, just to follow the legacy behavior.
+        Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent);
+        String packageName = (pendingIntent != null && pendingIntent.getIntent() != null)
+                ? pendingIntent.getIntent().getPackage() : null;
+        return new MediaButtonReceiverHolder(userId, pendingIntent,
+                packageName != null ? packageName : "");
+    }
+
+    private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent,
+            ComponentName componentName, @ComponentType int componentType) {
+        mUserId = userId;
+        mPendingIntent = pendingIntent;
+        mComponentName = componentName;
+        mPackageName = componentName.getPackageName();
+        mComponentType = componentType;
+    }
+
+    private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) {
+        mUserId = userId;
+        mPendingIntent = pendingIntent;
+        mComponentName = null;
+        mPackageName = packageName;
+        mComponentType = COMPONENT_TYPE_INVALID;
+    }
+
+    /**
+     * @return the user id
+     */
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /**
+     * @return package name that the media button receiver would be sent to.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Sends the media key event to the media button receiver.
+     * <p>
+     * This prioritizes using use pending intent for sending media key event.
+     *
+     * @param context context to be used to call PendingIntent#send
+     * @param keyEvent keyEvent to send
+     * @param resultCode result code to be used to call PendingIntent#send
+     *                   Ignored if there's no valid pending intent.
+     * @param onFinishedListener callback to be used to get result of PendingIntent#send.
+     *                           Ignored if there's no valid pending intent.
+     * @param handler handler to be used to call onFinishedListener
+     *                Ignored if there's no valid pending intent.
+     * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler)
+     */
+    public boolean send(Context context, KeyEvent keyEvent, String callingPackageName,
+            int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) {
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+        // TODO: Find a way to also send PID/UID in secure way.
+        mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName);
+
+        if (mPendingIntent != null) {
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent "
+                        + mPendingIntent);
+            }
+            try {
+                mPendingIntent.send(
+                        context, resultCode, mediaButtonIntent, onFinishedListener, handler);
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e);
+                return false;
+            }
+        } else if (mComponentName != null) {
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+                        + mComponentName + ", type=" + mComponentType);
+            }
+            mediaButtonIntent.setComponent(mComponentName);
+            UserHandle userHandle = UserHandle.of(mUserId);
+            try {
+                switch (mComponentType) {
+                    case COMPONENT_TYPE_ACTIVITY:
+                        context.startActivityAsUser(mediaButtonIntent, userHandle);
+                        break;
+                    case COMPONENT_TYPE_SERVICE:
+                        context.startForegroundServiceAsUser(mediaButtonIntent,
+                                userHandle);
+                        break;
+                    default:
+                        // Legacy behavior for other cases.
+                        context.sendBroadcastAsUser(mediaButtonIntent, userHandle);
+                }
+            } catch (Exception e) {
+                Log.w(TAG, "Error sending media button to the restored intent "
+                        + mComponentName + ", type=" + mComponentType, e);
+                return false;
+            }
+        } else {
+            // Leave log, just in case.
+            Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set");
+            return false;
+        }
+        return true;
+    }
+
+
+    @Override
+    public String toString() {
+        if (mPendingIntent != null) {
+            return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}";
+        }
+        return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}";
+    }
+
+    /**
+     * @return flattened string. Can be empty string if the MBR is created with implicit intent.
+     */
+    public String flattenToString() {
+        if (mComponentName == null) {
+            // We don't know which component would receive the key event.
+            return "";
+        }
+        return String.join(COMPONENT_NAME_USER_ID_DELIM,
+                mComponentName.toString(),
+                String.valueOf(mUserId),
+                String.valueOf(mComponentType));
+    }
+
+    /**
+     * Gets the type of the component
+     *
+     * @param context context
+     * @param componentName component name
+     * @return A component type
+     */
+    @ComponentType
+    private static int getComponentType(Context context, ComponentName componentName) {
+        if (componentName == null) {
+            return COMPONENT_TYPE_INVALID;
+        }
+        PackageManager pm = context.getPackageManager();
+        try {
+            ActivityInfo activityInfo = pm.getActivityInfo(componentName,
+                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.GET_ACTIVITIES);
+            if (activityInfo != null) {
+                return COMPONENT_TYPE_ACTIVITY;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        try {
+            ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.GET_SERVICES);
+            if (serviceInfo != null) {
+                return COMPONENT_TYPE_SERVICE;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        // Pick legacy behavior for BroadcastReceiver or unknown.
+        return COMPONENT_TYPE_BROADCAST;
+    }
+
+    private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) {
+        // Flag explanations.
+        // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+        //     filter apps regardless of the phone's locked/unlocked state.
+        // - GET_SERVICES: Return service
+        return createComponentName(pm.resolveService(intent,
+                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                        | PackageManager.GET_SERVICES));
+    }
+
+    private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent(
+            PackageManager pm, Intent intent) {
+        // Flag explanations.
+        // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+        //     filter apps regardless of the phone's locked/unlocked state.
+        List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(intent,
+                PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        return (resolveInfos != null && !resolveInfos.isEmpty())
+                ? createComponentName(resolveInfos.get(0)) : null;
+    }
+
+    private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) {
+        // Flag explanations.
+        // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+        //     Filter apps regardless of the phone's locked/unlocked state.
+        // - MATCH_DEFAULT_ONLY:
+        //     Implicit intent receiver should be set as default. Only needed for activity.
+        // - GET_ACTIVITIES: Return activity
+        return createComponentName(pm.resolveActivity(intent,
+                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                        | PackageManager.MATCH_DEFAULT_ONLY
+                        | PackageManager.GET_ACTIVITIES));
+    }
+
+    private static ComponentName createComponentName(ResolveInfo resolveInfo) {
+        if (resolveInfo == null) {
+            return null;
+        }
+        ComponentInfo componentInfo;
+        // Code borrowed from ResolveInfo#getComponentInfo().
+        if (resolveInfo.activityInfo != null) {
+            componentInfo = resolveInfo.activityInfo;
+        } else if (resolveInfo.serviceInfo != null) {
+            componentInfo = resolveInfo.serviceInfo;
+        } else {
+            // We're not interested in content provider.
+            return null;
+        }
+        // Code borrowed from ComponentInfo#getComponentName().
+        try {
+            return new ComponentName(componentInfo.packageName, componentInfo.name);
+        } catch (IllegalArgumentException | NullPointerException e) {
+            // This may be happen if resolveActivity() end up with matching multiple activities.
+            // see PackageManager#resolveActivity().
+            return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 7b17b4e..8358884 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -63,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 e22ea46..dd536ec 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -138,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);
@@ -419,6 +411,12 @@
             mActiveConnection.dispose();
             mActiveConnection = null;
             setAndNotifyProviderState(null);
+            synchronized (mLock) {
+                for (RoutingSessionInfo sessionInfo : mSessionInfos) {
+                    mCallback.onSessionReleased(this, sessionInfo);
+                }
+                mSessionInfos.clear();
+            }
         }
     }
 
@@ -518,14 +516,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 358cc29..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();
@@ -628,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();
@@ -713,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);
@@ -1459,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();
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/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 7bcbcd4..9f47b34 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -127,7 +127,7 @@
             new ArrayList<>();
 
     private long mFlags;
-    private PendingIntent mMediaButtonReceiver;
+    private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
     private PendingIntent mLaunchIntent;
 
     // TransportPerformer fields
@@ -220,8 +220,8 @@
      *
      * @return The pending intent set by the app or null.
      */
-    public PendingIntent getMediaButtonReceiver() {
-        return mMediaButtonReceiver;
+    public MediaButtonReceiverHolder getMediaButtonReceiver() {
+        return mMediaButtonReceiverHolder;
     }
 
     /**
@@ -471,7 +471,7 @@
                 + ", userId=" + mUserId);
         pw.println(indent + "package=" + mPackageName);
         pw.println(indent + "launchIntent=" + mLaunchIntent);
-        pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
+        pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder);
         pw.println(indent + "active=" + mIsActive);
         pw.println(indent + "flags=" + mFlags);
         pw.println(indent + "rating type=" + mRatingType);
@@ -833,12 +833,14 @@
 
         @Override
         public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
-            if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) {
-                return;
-            }
-            mMediaButtonReceiver = pi;
             final long token = Binder.clearCallingIdentity();
             try {
+                if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
+                        != 0) {
+                    return;
+                }
+                mMediaButtonReceiverHolder =
+                        MediaButtonReceiverHolder.create(mContext, mUserId, pi);
                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1529,5 +1531,4 @@
             msg.sendToTarget();
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 88b884e..7ffac06 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -18,22 +18,17 @@
 
 import static android.os.UserHandle.USER_ALL;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
-import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.media.AudioManager;
@@ -99,7 +94,7 @@
     private static final String TAG = "MediaSessionService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // Leave log for key event always.
-    private static final boolean DEBUG_KEY_EVENT = true;
+    static final boolean DEBUG_KEY_EVENT = true;
 
     private static final int WAKELOCK_TIMEOUT = 5000;
     private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
@@ -760,12 +755,6 @@
      * <p>The contents of this object is guarded by {@link #mLock}.
      */
     final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
-        public static final int COMPONENT_TYPE_INVALID = 0;
-        public static final int COMPONENT_TYPE_BROADCAST = 1;
-        public static final int COMPONENT_TYPE_ACTIVITY = 2;
-        public static final int COMPONENT_TYPE_SERVICE = 3;
-        private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
-
         private final int mFullUserId;
         private final MediaSessionStack mPriorityStack;
         private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord>
@@ -774,10 +763,7 @@
                 mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
         private final SparseIntArray mUidToSessionCount = new SparseIntArray();
 
-        private PendingIntent mLastMediaButtonReceiver;
-        private ComponentName mRestoredMediaButtonReceiver;
-        private int mRestoredMediaButtonReceiverComponentType;
-        private int mRestoredMediaButtonReceiverUserId;
+        private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder;
 
         private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
         private int mOnVolumeKeyLongPressListenerUid;
@@ -794,21 +780,9 @@
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
-            if (mediaButtonReceiverInfo == null) {
-                return;
-            }
-            String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
-            if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
-                return;
-            }
-            mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
-            mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
-            if (tokens.length == 3) {
-                mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
-            } else {
-                mRestoredMediaButtonReceiverComponentType =
-                        getComponentType(mRestoredMediaButtonReceiver);
-            }
+            mLastMediaButtonReceiverHolder =
+                    MediaButtonReceiverHolder.unflattenFromString(
+                            mContext, mediaButtonReceiverInfo);
         }
 
         public void destroySessionsForUserLocked(int userId) {
@@ -892,10 +866,7 @@
                     : mOnMediaKeyEventSessionChangedListeners.values()) {
                 pw.println(indent + "  from " + getCallingPackageName(cr.uid));
             }
-            pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
-            pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
-            pw.println(indent + "Restored MediaButtonReceiverComponentType: "
-                    + mRestoredMediaButtonReceiverComponentType);
+            pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder);
             mPriorityStack.dump(pw, indent);
         }
 
@@ -924,25 +895,12 @@
                 return;
             }
             MediaSessionRecord sessionRecord = (MediaSessionRecord) record;
-            PendingIntent receiver = sessionRecord.getMediaButtonReceiver();
-            mLastMediaButtonReceiver = receiver;
-            mRestoredMediaButtonReceiver = null;
-            mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
-
-            String mediaButtonReceiverInfo = "";
-            if (receiver != null) {
-                ComponentName component = receiver.getIntent().getComponent();
-                if (component != null
-                        && record.getPackageName().equals(component.getPackageName())) {
-                    String componentName = component.flattenToString();
-                    int componentType = getComponentType(component);
-                    mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
-                            componentName, String.valueOf(record.getUserId()),
-                            String.valueOf(componentType));
-                }
-            }
+            mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver();
+            String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null)
+                    ? "" : mLastMediaButtonReceiverHolder.flattenToString();
             Settings.Secure.putStringForUser(mContentResolver,
-                    Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
+                    Settings.System.MEDIA_BUTTON_RECEIVER,
+                    mediaButtonReceiverInfo,
                     mFullUserId);
         }
 
@@ -958,15 +916,9 @@
                     } else {
                         // TODO(jaewan): Implement
                     }
-                } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                    callback.onMediaKeyEventSessionChanged(
-                            mCurrentFullUserRecord.mLastMediaButtonReceiver
-                                    .getIntent().getComponent().getPackageName(),
-                            null);
-                } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
-                    callback.onMediaKeyEventSessionChanged(
-                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(),
-                            null);
+                } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) {
+                    String packageName = mLastMediaButtonReceiverHolder.getPackageName();
+                    callback.onMediaKeyEventSessionChanged(packageName, null);
                 }
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
@@ -985,35 +937,6 @@
                     ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
         }
 
-        private int getComponentType(@Nullable ComponentName componentName) {
-            if (componentName == null) {
-                return COMPONENT_TYPE_INVALID;
-            }
-            PackageManager pm = mContext.getPackageManager();
-            try {
-                ActivityInfo activityInfo = pm.getActivityInfo(componentName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.GET_ACTIVITIES);
-                if (activityInfo != null) {
-                    return COMPONENT_TYPE_ACTIVITY;
-                }
-            } catch (NameNotFoundException e) {
-            }
-            try {
-                ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.GET_SERVICES);
-                if (serviceInfo != null) {
-                    return COMPONENT_TYPE_SERVICE;
-                }
-            } catch (NameNotFoundException e) {
-            }
-            // Pick legacy behavior for BroadcastReceiver or unknown.
-            return COMPONENT_TYPE_BROADCAST;
-        }
-
         final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient {
             public final IOnMediaKeyEventDispatchedListener callback;
             public final int uid;
@@ -2166,79 +2089,31 @@
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to send callback", e);
                 }
-            } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
-                    || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+            } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) {
                 if (needWakeLock) {
                     mKeyEventReceiver.acquireWakeLockLocked();
                 }
-                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                // TODO: Find a way to also send PID/UID in secure way.
-                String callerPackageName =
+                String callingPackageName =
                         (asSystemService) ? mContext.getPackageName() : packageName;
-                mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
-                try {
-                    if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                        PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
-                        if (DEBUG_KEY_EVENT) {
-                            Log.d(TAG, "Sending " + keyEvent
-                                    + " to the last known PendingIntent " + receiver);
-                        }
-                        receiver.send(mContext,
-                                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
-                                mediaButtonIntent, mKeyEventReceiver, mHandler);
-                        ComponentName componentName = mCurrentFullUserRecord
-                                .mLastMediaButtonReceiver.getIntent().getComponent();
-                        if (componentName != null) {
-                            for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
-                                    : mCurrentFullUserRecord
-                                    .mOnMediaKeyEventDispatchedListeners.values()) {
-                                cr.callback.onMediaKeyEventDispatched(keyEvent,
-                                        componentName.getPackageName(), null);
-                            }
-                        }
-                    } else {
-                        ComponentName receiver =
-                                mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
-                        int componentType = mCurrentFullUserRecord
-                                .mRestoredMediaButtonReceiverComponentType;
-                        UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
-                                .mRestoredMediaButtonReceiverUserId);
-                        if (DEBUG_KEY_EVENT) {
-                            Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
-                                    + receiver + ", type=" + componentType);
-                        }
-                        mediaButtonIntent.setComponent(receiver);
+
+                MediaButtonReceiverHolder mediaButtonReceiverHolder =
+                        mCurrentFullUserRecord.mLastMediaButtonReceiverHolder;
+
+                boolean sent = mediaButtonReceiverHolder.send(
+                        mContext, keyEvent, callingPackageName,
+                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver,
+                        mHandler);
+                if (sent) {
+                    String pkgName = mediaButtonReceiverHolder.getPackageName();
+                    for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+                            : mCurrentFullUserRecord
+                            .mOnMediaKeyEventDispatchedListeners.values()) {
                         try {
-                            switch (componentType) {
-                                case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
-                                    mContext.startActivityAsUser(mediaButtonIntent, userHandle);
-                                    break;
-                                case FullUserRecord.COMPONENT_TYPE_SERVICE:
-                                    mContext.startForegroundServiceAsUser(mediaButtonIntent,
-                                            userHandle);
-                                    break;
-                                default:
-                                    // Legacy behavior for other cases.
-                                    mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
-                            }
-                        } catch (Exception e) {
-                            Log.w(TAG, "Error sending media button to the restored intent "
-                                    + receiver + ", type=" + componentType, e);
-                        }
-                        for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
-                                : mCurrentFullUserRecord
-                                .mOnMediaKeyEventDispatchedListeners.values()) {
-                            cr.callback.onMediaKeyEventDispatched(keyEvent,
-                                    receiver.getPackageName(), null);
+                            cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e);
                         }
                     }
-                } catch (CanceledException e) {
-                    Log.i(TAG, "Error sending key event to media button receiver "
-                            + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to send callback", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 798a781..b5dcea8 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -20,9 +20,11 @@
 import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
 
 import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.media.AudioRoutesInfo;
 import android.media.IAudioRoutesObserver;
@@ -107,6 +109,9 @@
             }
         });
         initializeSessionInfo();
+
+        mContext.registerReceiver(new VolumeChangeReceiver(),
+                new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
     }
 
     @Override
@@ -153,11 +158,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,
@@ -283,4 +283,44 @@
         }
         mCallback.onSessionUpdated(this, sessionInfo);
     }
+
+    private class VolumeChangeReceiver extends BroadcastReceiver {
+        // This will be called in the main thread.
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+                return;
+            }
+
+            final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+            if (streamType != AudioManager.STREAM_MUSIC) {
+                return;
+            }
+
+            final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+            final int oldVolume = intent.getIntExtra(
+                    AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
+
+            if (newVolume != oldVolume) {
+                String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+                if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
+                    for (int i = mBluetoothRoutes.size() - 1; i >= 0; i--) {
+                        MediaRoute2Info route = mBluetoothRoutes.get(i);
+                        if (TextUtils.equals(activeBtDeviceAddress, route.getId())) {
+                            mBluetoothRoutes.set(i,
+                                    new MediaRoute2Info.Builder(route)
+                                            .setVolume(newVolume)
+                                            .build());
+                            break;
+                        }
+                    }
+                } else {
+                    mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
+                            .setVolume(newVolume)
+                            .build();
+                }
+                publishRoutes();
+            }
+        }
+    }
 }
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/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index e8cb163..061cbd8 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -170,6 +170,11 @@
         mFileWriteHandler.post(rpr);
     }
 
+    public void deleteNotificationHistoryItem(String pkg, long postedTime) {
+        RemoveNotificationRunnable rnr = new RemoveNotificationRunnable(pkg, postedTime);
+        mFileWriteHandler.post(rnr);
+    }
+
     public void addNotification(final HistoricalNotification notification) {
         synchronized (mLock) {
             mBuffer.addNewNotificationToWrite(notification);
@@ -367,6 +372,49 @@
         }
     }
 
+    final class RemoveNotificationRunnable implements Runnable {
+        private String mPkg;
+        private long mPostedTime;
+        private NotificationHistory mNotificationHistory;
+
+        public RemoveNotificationRunnable(String pkg, long postedTime) {
+            mPkg = pkg;
+            mPostedTime = postedTime;
+        }
+
+        @VisibleForTesting
+        void setNotificationHistory(NotificationHistory nh) {
+            mNotificationHistory = nh;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "RemovePackageRunnable");
+            synchronized (mLock) {
+                // Remove from pending history
+                mBuffer.removeNotificationFromWrite(mPkg, mPostedTime);
+
+                Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
+                while (historyFileItr.hasNext()) {
+                    final AtomicFile af = historyFileItr.next();
+                    try {
+                        NotificationHistory notificationHistory = mNotificationHistory != null
+                                ? mNotificationHistory
+                                : new NotificationHistory();
+                        readLocked(af, notificationHistory,
+                                new NotificationHistoryFilter.Builder().build());
+                        if(notificationHistory.removeNotificationFromWrite(mPkg, mPostedTime)) {
+                            writeLocked(af, notificationHistory);
+                        }
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Cannot clean up file on notification removal "
+                                + af.getBaseFile().getName(), e);
+                    }
+                }
+            }
+        }
+    }
+
     public static final class NotificationHistoryFileAttrProvider implements
             NotificationHistoryDatabase.FileAttrProvider {
         final static String TAG = "NotifHistoryFileDate";
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 41bc29f..9aab0fd 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -130,7 +130,7 @@
         }
     }
 
-    public void onPackageRemoved(int userId, String packageName) {
+    public void onPackageRemoved(@UserIdInt int userId, String packageName) {
         synchronized (mLock) {
             if (!mUserUnlockedStates.get(userId, false)) {
                 if (mHistoryEnabled.get(userId, false)) {
@@ -150,6 +150,22 @@
         }
     }
 
+    public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
+        synchronized (mLock) {
+            int userId = UserHandle.getUserId(uid);
+            final NotificationHistoryDatabase userHistory =
+                    getUserHistoryAndInitializeIfNeededLocked(userId);
+            // TODO: it shouldn't be possible to delete a notification entry while the user is
+            // locked but we should handle it
+            if (userHistory == null) {
+                Slog.w(TAG, "Attempted to remove notif for locked/gone/disabled user "
+                        + userId);
+                return;
+            }
+            userHistory.deleteNotificationHistoryItem(pkg, postedTime);
+        }
+    }
+
     // TODO: wire this up to AMS when power button is long pressed
     public void triggerWriteToDisk() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f6276fb..7ffd899 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -193,6 +193,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
+import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
@@ -2681,18 +2682,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);
@@ -2730,7 +2737,7 @@
                 return;
             }
 
-            if (callback != null && !appIsForeground && !isSystemToast) {
+            if (callback != null && !appIsForeground && !isSystemToast && isCustom) {
                 boolean block;
                 long id = Binder.clearCallingIdentity();
                 try {
@@ -2774,7 +2781,7 @@
                         if (!isSystemToast) {
                             int count = 0;
                             final int N = mToastQueue.size();
-                            for (int i=0; i<N; i++) {
+                            for (int i = 0; i < N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
@@ -2814,7 +2821,7 @@
 
             if (pkg == null || token == null) {
                 Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token);
-                return ;
+                return;
             }
 
             synchronized (mToastQueue) {
@@ -2927,14 +2934,14 @@
 
         /**
          * Updates the enabled state for notifications for the given package (and uid).
-         * Additionally, this method marks the app importance as locked by the user, which means
+         * Additionally, this method marks the app importance as locked by the user, which
+         * means
          * that notifications from the app will <b>not</b> be considered for showing a
          * blocking helper.
          *
-         * @param pkg package that owns the notifications to update
-         * @param uid uid of the app providing notifications
+         * @param pkg     package that owns the notifications to update
+         * @param uid     uid of the app providing notifications
          * @param enabled whether notifications should be enabled for the app
-         *
          * @see #setNotificationsEnabledForPackage(String, int, boolean)
          */
         @Override
@@ -3025,6 +3032,12 @@
         }
 
         @Override
+        public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
+            checkCallerIsSystem();
+            mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime);
+        }
+
+        @Override
         public int getPackageImportance(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
@@ -3209,9 +3222,10 @@
 
         @Override
         public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
-                String channelId, boolean includeDeleted) {
+                String channelId, String conversationId, boolean includeDeleted) {
             checkCallerIsSystem();
-            return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
+            return mPreferencesHelper.getConversationNotificationChannel(
+                    pkg, uid, channelId, conversationId, true, includeDeleted);
         }
 
         @Override
@@ -3348,6 +3362,27 @@
         }
 
         @Override
+        public ParceledListSlice<ConversationChannelWrapper> getConversationsForPackage(String pkg,
+                int uid) {
+            enforceSystemOrSystemUI("getConversationsForPackage");
+            ArrayList<ConversationChannelWrapper> conversations =
+                    mPreferencesHelper.getConversations(pkg, uid);
+            for (ConversationChannelWrapper conversation : conversations) {
+                LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+                        .setPackage(pkg)
+                        .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                        .setShortcutIds(Arrays.asList(
+                                conversation.getNotificationChannel().getConversationId()));
+                List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(
+                        query, UserHandle.of(UserHandle.getUserId(uid)));
+                if (shortcuts != null && !shortcuts.isEmpty()) {
+                    conversation.setShortcutInfo(shortcuts.get(0));
+                }
+            }
+            return new ParceledListSlice<>(conversations);
+        }
+
+        @Override
         public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(
                 String pkg, int uid, String groupId, boolean includeDeleted) {
             enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage");
@@ -5387,8 +5422,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;
         }
 
@@ -7428,13 +7463,15 @@
     }
 
     @GuardedBy("mNotificationLock")
-    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason,
+    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
+            @NotificationListenerService.NotificationCancelReason int reason,
             boolean wasPosted, String listenerName) {
         cancelNotificationLocked(r, sendDelete, reason, -1, -1, wasPosted, listenerName);
     }
 
     @GuardedBy("mNotificationLock")
-    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason,
+    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
+            @NotificationListenerService.NotificationCancelReason int reason,
             int rank, int count, boolean wasPosted, String listenerName) {
         final String canceledKey = r.getKey();
 
@@ -7552,6 +7589,10 @@
         EventLogTags.writeNotificationCanceled(canceledKey, reason,
                 r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                 rank, count, listenerName);
+        if (wasPosted) {
+            mNotificationRecordLogger.logNotificationCancelled(r, reason,
+                    r.getStats().getDismissalSurface());
+        }
     }
 
     @VisibleForTesting
@@ -7594,7 +7635,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);
                 }
@@ -7631,6 +7672,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);
         }
@@ -9010,59 +9053,64 @@
         @GuardedBy("mNotificationLock")
         private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                 boolean notifyAllListeners) {
-            // Lazily initialized snapshots of the notification.
-            StatusBarNotification sbn = r.getSbn();
-            StatusBarNotification oldSbn = (old != null) ? old.getSbn() : 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);
             }
         }
 
@@ -9243,7 +9291,7 @@
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (hasCompanionDevice(serviceInfo)) {
+                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
                         notifyNotificationChannelChanged(
                                 serviceInfo, pkg, user, channel, modificationType);
                     }
@@ -9263,7 +9311,7 @@
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (hasCompanionDevice(serviceInfo)) {
+                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
                         notifyNotificationChannelGroupChanged(
                                 serviceInfo, pkg, user, group, modificationType);
                     }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 8d8511f..fc2d9e7 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -16,10 +16,16 @@
 
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
+
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.Person;
 import android.os.Bundle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -44,22 +50,144 @@
             int position, int buzzBeepBlink);
 
     /**
+     * Logs a notification cancel / dismiss event using UiEventReported (event ids from the
+     * NotificationCancelledEvents enum).
+     * @param r The NotificationRecord. If null, no action is taken.
+     * @param reason The reason the notification was canceled.
+     * @param dismissalSurface The surface the notification was dismissed from.
+     */
+    void logNotificationCancelled(@Nullable NotificationRecord r,
+            @NotificationListenerService.NotificationCancelReason int reason,
+            @NotificationStats.DismissalSurface int dismissalSurface);
+
+    /**
      * The UiEvent enums that this class can log.
      */
-    enum NotificationReportedEvents implements UiEventLogger.UiEventEnum {
-        INVALID(0),
+    enum NotificationReportedEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "New notification enqueued to post")
         NOTIFICATION_POSTED(162),
-        @UiEvent(doc = "Notification substantially updated")
+        @UiEvent(doc = "Notification substantially updated, or alerted again.")
         NOTIFICATION_UPDATED(163);
 
         private final int mId;
-        NotificationReportedEvents(int id) {
+        NotificationReportedEvent(int id) {
             mId = id;
         }
         @Override public int getId() {
             return mId;
         }
+
+        public static NotificationReportedEvent fromRecordPair(NotificationRecordPair p) {
+            return (p.old != null) ? NotificationReportedEvent.NOTIFICATION_UPDATED :
+                            NotificationReportedEvent.NOTIFICATION_POSTED;
+        }
+    }
+
+    enum NotificationCancelledEvent implements UiEventLogger.UiEventEnum {
+        INVALID(0),
+        @UiEvent(doc = "Notification was canceled due to a notification click.")
+        NOTIFICATION_CANCEL_CLICK(164),
+        @UiEvent(doc = "Notification was canceled due to a user dismissal, surface not specified.")
+        NOTIFICATION_CANCEL_USER_OTHER(165),
+        @UiEvent(doc = "Notification was canceled due to a user dismiss-all (from the notification"
+                + " shade).")
+        NOTIFICATION_CANCEL_USER_CANCEL_ALL(166),
+        @UiEvent(doc = "Notification was canceled due to an inflation error.")
+        NOTIFICATION_CANCEL_ERROR(167),
+        @UiEvent(doc = "Notification was canceled by the package manager modifying the package.")
+        NOTIFICATION_CANCEL_PACKAGE_CHANGED(168),
+        @UiEvent(doc = "Notification was canceled by the owning user context being stopped.")
+        NOTIFICATION_CANCEL_USER_STOPPED(169),
+        @UiEvent(doc = "Notification was canceled by the user banning the package.")
+        NOTIFICATION_CANCEL_PACKAGE_BANNED(170),
+        @UiEvent(doc = "Notification was canceled by the app canceling this specific notification.")
+        NOTIFICATION_CANCEL_APP_CANCEL(171),
+        @UiEvent(doc = "Notification was canceled by the app cancelling all its notifications.")
+        NOTIFICATION_CANCEL_APP_CANCEL_ALL(172),
+        @UiEvent(doc = "Notification was canceled by a listener reporting a user dismissal.")
+        NOTIFICATION_CANCEL_LISTENER_CANCEL(173),
+        @UiEvent(doc = "Notification was canceled by a listener reporting a user dismiss all.")
+        NOTIFICATION_CANCEL_LISTENER_CANCEL_ALL(174),
+        @UiEvent(doc = "Notification was canceled because it was a member of a canceled group.")
+        NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED(175),
+        @UiEvent(doc = "Notification was canceled because it was an invisible member of a group.")
+        NOTIFICATION_CANCEL_GROUP_OPTIMIZATION(176),
+        @UiEvent(doc = "Notification was canceled by the device administrator suspending the "
+                + "package.")
+        NOTIFICATION_CANCEL_PACKAGE_SUSPENDED(177),
+        @UiEvent(doc = "Notification was canceled by the owning managed profile being turned off.")
+        NOTIFICATION_CANCEL_PROFILE_TURNED_OFF(178),
+        @UiEvent(doc = "Autobundled summary notification was canceled because its group was "
+                + "unbundled")
+        NOTIFICATION_CANCEL_UNAUTOBUNDLED(179),
+        @UiEvent(doc = "Notification was canceled by the user banning the channel.")
+        NOTIFICATION_CANCEL_CHANNEL_BANNED(180),
+        @UiEvent(doc = "Notification was snoozed.")
+        NOTIFICATION_CANCEL_SNOOZED(181),
+        @UiEvent(doc = "Notification was canceled due to timeout")
+        NOTIFICATION_CANCEL_TIMEOUT(182),
+        // Values 183-189 reserved for future system dismissal reasons
+        @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.")
+        NOTIFICATION_CANCEL_USER_PEEK(190),
+        @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
+        NOTIFICATION_CANCEL_USER_AOD(191),
+        @UiEvent(doc = "Notification was canceled due to user dismissal from the notification"
+                + " shade.")
+        NOTIFICATION_CANCEL_USER_SHADE(192),
+        @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
+        NOTIFICATION_CANCEL_USER_LOCKSCREEN(193);
+
+        private final int mId;
+        NotificationCancelledEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+        public static NotificationCancelledEvent fromCancelReason(
+                @NotificationListenerService.NotificationCancelReason int reason,
+                @NotificationStats.DismissalSurface int surface) {
+            // Shouldn't be possible to get a non-dismissed notification here.
+            if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) {
+                if (NotificationManagerService.DBG) {
+                    throw new IllegalArgumentException("Unexpected surface " + surface);
+                }
+                return INVALID;
+            }
+            // Most cancel reasons do not have a meaningful surface. Reason codes map directly
+            // to NotificationCancelledEvent codes.
+            if (surface == NotificationStats.DISMISSAL_OTHER) {
+                if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
+                    return NotificationCancelledEvent.values()[reason];
+                }
+                if (NotificationManagerService.DBG) {
+                    throw new IllegalArgumentException("Unexpected cancel reason " + reason);
+                }
+                return INVALID;
+            }
+            // User cancels have a meaningful surface, which we differentiate by. See b/149038335
+            // for caveats.
+            if (reason != REASON_CANCEL) {
+                if (NotificationManagerService.DBG) {
+                    throw new IllegalArgumentException("Unexpected cancel with surface " + reason);
+                }
+                return INVALID;
+            }
+            switch (surface) {
+                case NotificationStats.DISMISSAL_PEEK:
+                    return NOTIFICATION_CANCEL_USER_PEEK;
+                case NotificationStats.DISMISSAL_AOD:
+                    return NOTIFICATION_CANCEL_USER_AOD;
+                case NotificationStats.DISMISSAL_SHADE:
+                    return NOTIFICATION_CANCEL_USER_SHADE;
+                default:
+                    if (NotificationManagerService.DBG) {
+                        throw new IllegalArgumentException("Unexpected surface for user-dismiss "
+                                + reason);
+                    }
+                    return INVALID;
+            }
+        }
     }
 
     /**
@@ -88,7 +216,8 @@
                 return true;
             }
 
-            return !(Objects.equals(r.getSbn().getChannelIdLogTag(), old.getSbn().getChannelIdLogTag())
+            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())
@@ -97,11 +226,6 @@
                     && (r.getImportance() == old.getImportance()));
         }
 
-        NotificationReportedEvents getUiEvent() {
-            return (old != null) ? NotificationReportedEvents.NOTIFICATION_UPDATED :
-                    NotificationReportedEvents.NOTIFICATION_POSTED;
-        }
-
         /**
          * @return hash code for the notification style class, or 0 if none exists.
          */
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 4974c30..015d280 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.util.FrameworkStatsLog;
 
 /**
@@ -24,6 +26,8 @@
  */
 public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
 
+    UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+
     @Override
     public void logNotificationReported(NotificationRecord r, NotificationRecord old,
             int position, int buzzBeepBlink) {
@@ -32,7 +36,7 @@
             return;
         }
         FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
-                /* int32 event_id = 1 */ p.getUiEvent().getId(),
+                /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(),
                 /* int32 uid = 2 */ r.getUid(),
                 /* string package_name = 3 */ r.getSbn().getPackageName(),
                 /* int32 instance_id = 4 */ p.getInstanceId(),
@@ -61,9 +65,10 @@
         );
     }
 
-
-
-
-
-
+    @Override
+    public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
+        mUiEventLogger.logWithInstanceId(
+                NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface),
+                r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId());
+    }
 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index fe39322..a244c48 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.RankingHelperProto;
 import android.text.TextUtils;
@@ -1176,6 +1177,43 @@
         return groups;
     }
 
+    public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) {
+        Objects.requireNonNull(pkg);
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
+            if (r == null) {
+                return new ArrayList<>();
+            }
+            ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
+            int N = r.channels.size();
+            for (int i = 0; i < N; i++) {
+                final NotificationChannel nc = r.channels.valueAt(i);
+                if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()) {
+                    ConversationChannelWrapper conversation = new ConversationChannelWrapper();
+                    conversation.setNotificationChannel(nc);
+                    conversation.setParentChannelLabel(
+                            r.channels.get(nc.getParentChannelId()).getName());
+                    boolean blockedByGroup = false;
+                    if (nc.getGroup() != null) {
+                        NotificationChannelGroup group = r.groups.get(nc.getGroup());
+                        if (group != null) {
+                            if (group.isBlocked()) {
+                                blockedByGroup = true;
+                            } else {
+                                conversation.setGroupLabel(group.getName());
+                            }
+                        }
+                    }
+                    if (!blockedByGroup) {
+                        conversations.add(conversation);
+                    }
+                }
+            }
+
+            return conversations;
+        }
+    }
+
     @Override
     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
             boolean includeDeleted) {
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/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 42bc464..a440c62 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -36,6 +36,7 @@
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.sysprop.ApexProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -46,6 +47,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.utils.TimingsTraceAndSlog;
 
 import com.google.android.collect.Lists;
 
@@ -375,8 +377,11 @@
 
         @Override
         public List<ActiveApexInfo> getActiveApexInfos() {
+            final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
+                    Trace.TRACE_TAG_APEX_MANAGER);
             synchronized (mLock) {
                 if (mActiveApexInfosCache == null) {
+                    t.traceBegin("getActiveApexInfos_noCache");
                     try {
                         mActiveApexInfosCache = new ArraySet<>();
                         final ApexInfo[] activePackages = mApexService.getActivePackages();
@@ -387,6 +392,7 @@
                     } catch (RemoteException e) {
                         Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
                     }
+                    t.traceEnd();
                 }
                 if (mActiveApexInfosCache != null) {
                     return new ArrayList<>(mActiveApexInfosCache);
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 3ad1207..d629b54 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -33,7 +33,7 @@
 import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo;
 import android.content.pm.parsing.ComponentParseUtils.ParsedProvider;
 import android.content.pm.parsing.ComponentParseUtils.ParsedService;
-import android.net.Uri;
+import android.os.Binder;
 import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -86,10 +86,10 @@
     private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
 
     /**
-     * A mapping from the set of App IDs that query others via intent to the list
-     * of packages that the intents resolve to.
+     * A mapping from the set of App IDs that query others via component match to the list
+     * of packages that the they resolve to.
      */
-    private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>();
+    private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
 
     /**
      * A set of App IDs that are always queryable by any package, regardless of their manifest
@@ -171,11 +171,13 @@
         @Override
         public boolean packageIsEnabled(AndroidPackage pkg) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled");
+            final long token = Binder.clearCallingIdentity();
             try {
                 // TODO(b/135203078): Do not use toAppInfo
                 return mInjector.getCompatibility().isChangeEnabled(
                         PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState());
             } finally {
+                Binder.restoreCallingIdentity(token);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
         }
@@ -203,16 +205,19 @@
     }
 
     /** Returns true if the querying package may query for the potential target package */
-    private static boolean canQueryViaIntent(AndroidPackage querying,
+    private static boolean canQueryViaComponents(AndroidPackage querying,
             AndroidPackage potentialTarget) {
-        if (querying.getQueriesIntents() == null) {
-            return false;
-        }
-        for (Intent intent : querying.getQueriesIntents()) {
-            if (matches(intent, potentialTarget)) {
-                return true;
+        if (querying.getQueriesIntents() != null) {
+            for (Intent intent : querying.getQueriesIntents()) {
+                if (matchesIntentFilters(intent, potentialTarget)) {
+                    return true;
+                }
             }
         }
+        if (querying.getQueriesProviders() != null
+                && matchesProviders(querying.getQueriesProviders(), potentialTarget)) {
+            return true;
+        }
         return false;
     }
 
@@ -235,24 +240,25 @@
         return false;
     }
 
-    private static boolean matches(Intent intent, AndroidPackage potentialTarget) {
+    private static boolean matchesProviders(
+            Set<String> queriesAuthorities, AndroidPackage potentialTarget) {
         for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) {
             ParsedProvider provider = potentialTarget.getProviders().get(p);
             if (!provider.isExported()) {
                 continue;
             }
-            final Uri data = intent.getData();
-            if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
-                    || provider.getAuthority() == null) {
-                continue;
-            }
-            StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
+            StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";",
+                    false);
             while (authorities.hasMoreElements()) {
-                if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
+                if (queriesAuthorities.contains(authorities.nextToken())) {
                     return true;
                 }
             }
         }
+        return false;
+    }
+
+    private static boolean matchesIntentFilters(Intent intent, AndroidPackage potentialTarget) {
         for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
             ParsedService service = potentialTarget.getServices().get(s);
             if (!service.exported) {
@@ -365,8 +371,8 @@
                 final AndroidPackage existingPkg = existingSetting.pkg;
                 // let's evaluate the ability of already added packages to see this new package
                 if (!newIsForceQueryable) {
-                    if (canQueryViaIntent(existingPkg, newPkg)) {
-                        mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId);
+                    if (canQueryViaComponents(existingPkg, newPkg)) {
+                        mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId);
                     }
                     if (canQueryViaPackage(existingPkg, newPkg)
                             || canQueryAsInstaller(existingSetting, newPkg)) {
@@ -375,8 +381,8 @@
                 }
                 // now we'll evaluate our new package's ability to see existing packages
                 if (!mForceQueryable.contains(existingSetting.appId)) {
-                    if (canQueryViaIntent(newPkg, existingPkg)) {
-                        mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId);
+                    if (canQueryViaComponents(newPkg, existingPkg)) {
+                        mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId);
                     }
                     if (canQueryViaPackage(newPkg, existingPkg)
                             || canQueryAsInstaller(newPkgSetting, existingPkg)) {
@@ -424,9 +430,9 @@
             }
         }
 
-        mQueriesViaIntent.remove(setting.appId);
-        for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) {
-            mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId);
+        mQueriesViaComponent.remove(setting.appId);
+        for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+            mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId);
         }
         mQueriesViaPackage.remove(setting.appId);
         for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
@@ -591,10 +597,10 @@
                 Trace.endSection();
             }
             try {
-                Trace.beginSection("mQueriesViaIntent");
-                if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
+                Trace.beginSection("mQueriesViaComponent");
+                if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
                     if (DEBUG_LOGGING) {
-                        log(callingSetting, targetPkgSetting, "queries intent");
+                        log(callingSetting, targetPkgSetting, "queries component");
                     }
                     return false;
                 }
@@ -729,7 +735,7 @@
         pw.println("  queries via package name:");
         dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, "    ", expandPackages);
         pw.println("  queries via intent:");
-        dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, "    ", expandPackages);
+        dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, "    ", expandPackages);
         pw.println("  queryable via interaction:");
         for (int user : users) {
             pw.append("    User ").append(Integer.toString(user)).println(":");
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..0cd1c25 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;
@@ -659,10 +661,27 @@
             }
         }
 
+        private void ensureStrictAccessShortcutsPermission(@NonNull String callingPackage) {
+            verifyCallingPackage(callingPackage);
+            if (!injectHasAccessShortcutsPermission(injectBinderCallingPid(),
+                    injectBinderCallingUid())) {
+                throw new SecurityException("Caller can't access shortcut information");
+            }
+        }
+
+        /**
+         * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
+         */
+        @VisibleForTesting
+        boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
+            return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
+                    callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+        }
+
         @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 +690,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);
@@ -693,6 +727,30 @@
         }
 
         @Override
+        public void cacheShortcuts(String callingPackage, String packageName, List<String> ids,
+                UserHandle targetUser) {
+            ensureStrictAccessShortcutsPermission(callingPackage);
+            if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) {
+                return;
+            }
+
+            mShortcutServiceInternal.cacheShortcuts(getCallingUserId(),
+                    callingPackage, packageName, ids, targetUser.getIdentifier());
+        }
+
+        @Override
+        public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids,
+                UserHandle targetUser) {
+            ensureStrictAccessShortcutsPermission(callingPackage);
+            if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) {
+                return;
+            }
+
+            mShortcutServiceInternal.uncacheShortcuts(getCallingUserId(),
+                    callingPackage, packageName, ids, targetUser.getIdentifier());
+        }
+
+        @Override
         public int getShortcutIconResId(String callingPackage, String packageName, String id,
                 int targetUserId) {
             ensureShortcutPermission(callingPackage);
@@ -1137,7 +1195,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/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index e550bae..482fc49 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -344,18 +344,10 @@
                 int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        if (onIncremental) {
-                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
-                                    "incrementalNativeBinaries");
-                            abi32 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
-                                    handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
-                                    useIsaSpecificSubdirs);
-                        } else {
-                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                            abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                    nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
-                                    useIsaSpecificSubdirs);
-                        }
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                        abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+                                useIsaSpecificSubdirs, onIncremental);
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi32 = NativeLibraryHelper.findSupportedAbi(
@@ -375,18 +367,10 @@
 
                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        if (onIncremental) {
-                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
-                                    "incrementalNativeBinaries");
-                            abi64 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
-                                    handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
-                                    useIsaSpecificSubdirs);
-                        } else {
-                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
-                                    useIsaSpecificSubdirs);
-                        }
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                        abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+                                useIsaSpecificSubdirs, onIncremental);
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi64 = NativeLibraryHelper.findSupportedAbi(
@@ -437,15 +421,9 @@
 
                 final int copyRet;
                 if (extractLibs) {
-                    if (onIncremental) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "incrementalNativeBinaries");
-                        copyRet = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
-                                handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
-                    } else {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
-                    }
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                    copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                            nativeLibraryRoot, abiList, useIsaSpecificSubdirs, onIncremental);
                 } else {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                     copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 9116c40..33ef2d4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -129,6 +129,13 @@
     /** Upper bound on number of historical sessions for a UID */
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
 
+    /**
+     * Allow verification-skipping if it's a development app installed through ADB with
+     * disable verification flag specified.
+     */
+    private static final int ADB_DEV_MODE = PackageManager.INSTALL_FROM_ADB
+            | PackageManager.INSTALL_ALLOW_TEST;
+
     private final Context mContext;
     private final PackageManagerService mPm;
     private final ApexManager mApexManager;
@@ -531,8 +538,10 @@
             params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
         }
 
-        if (callingUid != Process.SYSTEM_UID) {
-            // Only system_server can use INSTALL_DISABLE_VERIFICATION.
+        if (callingUid != Process.SYSTEM_UID
+                && (params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) {
+            // Only system_server or tools under specific conditions (test app installed
+            // through ADB, and verification disabled flag specified) can disable verification.
             params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index bf7bebd..944280d 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();
                 }
             }
 
@@ -1660,10 +1681,7 @@
                 mInternalProgress = 0.5f;
                 computeProgressLocked(true);
 
-                // Unpack native libraries for non-incremental installation
-                if (!isIncrementalInstallation()) {
-                    extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
-                }
+                extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
             }
 
             // We've reached point of no return; call into PMS to install the stage.
@@ -1786,8 +1804,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 +1814,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)) {
@@ -2236,7 +2257,7 @@
         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
     }
 
-    private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
+    private void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
             throws PackageManagerException {
         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
         if (!inherit) {
@@ -2248,7 +2269,7 @@
         try {
             handle = NativeLibraryHelper.Handle.create(packageDir);
             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
-                    abiOverride);
+                    abiOverride, isIncrementalInstallation());
             if (res != PackageManager.INSTALL_SUCCEEDED) {
                 throw new PackageManagerException(res,
                         "Failed to extract native libraries, res=" + res);
@@ -3096,7 +3117,8 @@
         }
 
         if (grantedRuntimePermissions.size() > 0) {
-            params.grantedRuntimePermissions = (String[]) grantedRuntimePermissions.toArray();
+            params.grantedRuntimePermissions =
+                    grantedRuntimePermissions.toArray(EmptyArray.STRING);
         }
 
         if (whitelistedRestrictedPermissions.size() > 0) {
@@ -3115,7 +3137,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 8e5ff66..88c048c 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;
@@ -1658,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)
@@ -1668,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
@@ -1995,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;
 
@@ -2096,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*/,
@@ -3621,7 +3627,7 @@
             try {
                 handle = NativeLibraryHelper.Handle.create(dstCodePath);
                 ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                        null /*abiOverride*/);
+                        null /*abiOverride*/, false /*isIncremental*/);
             } catch (IOException e) {
                 logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
                         + "; pkg: " + packageName);
@@ -13344,42 +13350,53 @@
      *
      * @return true if verification should be performed
      */
-    private boolean isVerificationEnabled(int userId, int installFlags, int installerUid) {
+    private boolean isVerificationEnabled(
+            PackageInfoLite pkgInfoLite, int userId, int installFlags, int installerUid) {
         if (!DEFAULT_VERIFY_ENABLE) {
             return false;
         }
 
-        if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
-            return false;
-        }
-
         // Check if installing from ADB
         if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
             if (isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
                 return true;
             }
-            // Check if the developer does not want package verification for ADB installs
+            // Check if the developer wants to skip verification for ADB installs
+            if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
+                synchronized (mLock) {
+                    if (mSettings.mPackages.get(pkgInfoLite.packageName) == null) {
+                        // Always verify fresh install
+                        return true;
+                    }
+                }
+                // Only skip when apk is debuggable
+                return !pkgInfoLite.debuggable;
+            }
             return Global.getInt(mContext.getContentResolver(),
                     Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
-        } else {
-            // only when not installed from ADB, skip verification for instant apps when
-            // the installer and verifier are the same.
-            if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
-                if (mInstantAppInstallerActivity != null
-                        && mInstantAppInstallerActivity.packageName.equals(
-                                mRequiredVerifierPackage)) {
-                    try {
-                        mInjector.getAppOpsManager()
-                                .checkPackage(installerUid, mRequiredVerifierPackage);
-                        if (DEBUG_VERIFY) {
-                            Slog.i(TAG, "disable verification for instant app");
-                        }
-                        return false;
-                    } catch (SecurityException ignore) { }
-                }
-            }
-            return true;
         }
+
+        if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
+            return false;
+        }
+
+        // only when not installed from ADB, skip verification for instant apps when
+        // the installer and verifier are the same.
+        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
+            if (mInstantAppInstallerActivity != null
+                    && mInstantAppInstallerActivity.packageName.equals(
+                            mRequiredVerifierPackage)) {
+                try {
+                    mInjector.getAppOpsManager()
+                            .checkPackage(installerUid, mRequiredVerifierPackage);
+                    if (DEBUG_VERIFY) {
+                        Slog.i(TAG, "disable verification for instant app");
+                    }
+                    return false;
+                } catch (SecurityException ignore) { }
+            }
+        }
+        return true;
     }
 
     /**
@@ -13978,9 +13995,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;
@@ -13989,6 +14008,7 @@
             this.appId = appId;
             this.seinfo = seinfo;
             this.targetSdkVersion = targetSdkVersion;
+            this.fromCodePath = fromCodePath;
         }
     }
 
@@ -14114,13 +14134,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;
@@ -14136,40 +14157,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
@@ -14537,7 +14560,7 @@
                     verificationInfo == null ? -1 : verificationInfo.installerUid;
             if (!origin.existing && requiredUid != -1
                     && isVerificationEnabled(
-                    verifierUser.getIdentifier(), installFlags, installerUid)) {
+                            pkgLite, verifierUser.getIdentifier(), installFlags, installerUid)) {
                 final Intent verification = new Intent(
                         Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
                 verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -14772,6 +14795,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
@@ -14785,7 +14809,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;
@@ -14803,6 +14827,7 @@
             this.installReason = installReason;
             this.forceQueryableOverride = forceQueryableOverride;
             this.mMultiPackageInstallParams = multiPackageInstallParams;
+            this.mDataLoaderType = dataLoaderType;
         }
 
         /** New install */
@@ -14812,7 +14837,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();
@@ -14903,7 +14929,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;
         }
@@ -14943,12 +14970,13 @@
                 return ret;
             }
 
+            final boolean isIncremental = isIncrementalPath(codeFile.getAbsolutePath());
             final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
             NativeLibraryHelper.Handle handle = null;
             try {
                 handle = NativeLibraryHelper.Handle.create(codeFile);
                 ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                        abiOverride);
+                        abiOverride, isIncremental);
             } catch (IOException e) {
                 Slog.e(TAG, "Copying native libraries failed", e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
@@ -14983,9 +15011,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());
                 }
@@ -15104,7 +15130,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;
@@ -22054,6 +22081,7 @@
         final PackageFreezer freezer;
         final int[] installedUserIds;
         final boolean isCurrentLocationExternal;
+        final String fromCodePath;
 
         // reader
         synchronized (mLock) {
@@ -22110,6 +22138,7 @@
             targetSdkVersion = pkg.getTargetSdkVersion();
             freezer = freezePackage(packageName, "movePackageInternal");
             installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
+            fromCodePath = pkg.getCodePath();
         }
 
         final Bundle extras = new Bundle();
@@ -22238,7 +22267,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;
         }
@@ -22251,7 +22280,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;
 
@@ -22757,6 +22787,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
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 71a5545..9395c97 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -794,6 +794,7 @@
         ret.verifiers = pkg.verifiers;
         ret.recommendedInstallLocation = recommendedInstallLocation;
         ret.multiArch = pkg.multiArch;
+        ret.debuggable = pkg.debuggable;
 
         return ret;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index bb69680..cb94043 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2757,6 +2757,9 @@
                 case "--no-wait":
                     params.mWaitForStagedSessionReady = false;
                     break;
+                case "--skip-verification":
+                    sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f7889ea..377fd16 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;
@@ -1703,7 +1704,7 @@
             ShortcutInfo.validateIcon(shortcut.getIcon());
         }
 
-        shortcut.replaceFlags(0);
+        shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED);
     }
 
     private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
@@ -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)) {
@@ -2792,6 +2758,68 @@
         }
 
         @Override
+        public void cacheShortcuts(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull List<String> shortcutIds, int userId) {
+            updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
+                    userId, /* doCache= */ true);
+        }
+
+        @Override
+        public void uncacheShortcuts(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull List<String> shortcutIds, int userId) {
+            updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
+                    userId, /* doCache= */ false);
+        }
+
+        private void updateCachedShortcutsInternal(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull List<String> shortcutIds, int userId, boolean doCache) {
+            // Calling permission must be checked by LauncherAppsImpl.
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+            Objects.requireNonNull(shortcutIds, "shortcutIds");
+
+            synchronized (mLock) {
+                throwIfUserLockedL(userId);
+                throwIfUserLockedL(launcherUserId);
+
+                final int idSize = shortcutIds.size();
+                final ShortcutPackage sp = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (idSize == 0 || sp == null) {
+                    return;
+                }
+
+                for (int i = 0; i < idSize; i++) {
+                    final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i));
+                    final ShortcutInfo si = sp.findShortcutById(id);
+                    if (si == null || doCache == si.isCached()) {
+                        continue;
+                    }
+
+                    if (doCache) {
+                        if (si.isDynamic() && si.isLongLived()) {
+                            si.addFlags(ShortcutInfo.FLAG_CACHED);
+                        } else {
+                            Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring"
+                                    + "shortcut " + si.getId());
+                        }
+                    } else {
+                        if (si.isDynamic()) {
+                            si.clearFlags(ShortcutInfo.FLAG_CACHED);
+                        } else {
+                            sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+                        }
+                    }
+                }
+            }
+            packageShortcutsChanged(packageName, userId);
+
+            verifyStates();
+        }
+
+        @Override
         public Intent[] createShortcutIntents(int launcherUserId,
                 @NonNull String callingPackage,
                 @NonNull String packageName, @NonNull String shortcutId, int userId,
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 8935453..614cc3f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -57,6 +57,7 @@
 import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.text.TextUtils;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -67,6 +68,7 @@
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.rollback.WatchdogRollbackLogger;
 
 import java.io.File;
 import java.io.IOException;
@@ -99,6 +101,10 @@
     @GuardedBy("mStagedSessions")
     private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
 
+    @GuardedBy("mFailedPackageNames")
+    private final List<String> mFailedPackageNames = new ArrayList<>();
+    private String mNativeFailureReason;
+
     StagingManager(PackageInstallerService pi, Context context) {
         mPi = pi;
         mContext = context;
@@ -441,6 +447,22 @@
         }
     }
 
+    /**
+     *  Prepares for the logging of apexd reverts by storing the native failure reason if necessary,
+     *  and adding the package name of the session which apexd reverted to the list of reverted
+     *  session package names.
+     *  Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent.
+     */
+    private void prepareForLoggingApexdRevert(@NonNull PackageInstallerSession session,
+            @NonNull String nativeFailureReason) {
+        synchronized (mFailedPackageNames) {
+            mNativeFailureReason = nativeFailureReason;
+            if (session.getPackageName() != null) {
+                mFailedPackageNames.add(session.getPackageName());
+            }
+        }
+    }
+
     private void resumeSession(@NonNull PackageInstallerSession session) {
         Slog.d(TAG, "Resuming session " + session.sessionId);
 
@@ -450,6 +472,12 @@
             // Check with apexservice whether the apex packages have been activated.
             apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
 
+            // Prepare for logging a native crash during boot, if one occurred.
+            if (apexSessionInfo != null && !TextUtils.isEmpty(
+                    apexSessionInfo.crashingNativeProcess)) {
+                prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
+            }
+
             if (apexSessionInfo != null && apexSessionInfo.isVerified) {
                 // Session has been previously submitted to apexd, but didn't complete all the
                 // pre-reboot verification, perhaps because the device rebooted in the meantime.
@@ -955,12 +983,23 @@
         }
     }
 
+    private void logFailedApexSessionsIfNecessary() {
+        synchronized (mFailedPackageNames) {
+            if (!mFailedPackageNames.isEmpty()) {
+                WatchdogRollbackLogger.logApexdRevert(mContext,
+                        mFailedPackageNames, mNativeFailureReason);
+            }
+        }
+    }
+
     void systemReady() {
         // Register the receiver of boot completed intent for staging manager.
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context ctx, Intent intent) {
                 mPreRebootVerificationHandler.readyToStart();
+                BackgroundThread.getExecutor().execute(
+                        () -> logFailedApexSessionsIfNecessary());
                 ctx.unregisterReceiver(this);
             }
         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0cb8f495..1c02161 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1901,7 +1901,7 @@
 
     @Override
     public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) {
-        checkManageUsersPermission("hasBaseUserRestriction");
+        checkManageOrCreateUsersPermission("hasBaseUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
             return false;
         }
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/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 2c7795a..43d4596 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -39,9 +39,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PermissionInfo;
-import android.content.pm.parsing.AndroidPackage;
 import android.os.Build;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -52,14 +50,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongSparseLongArray;
-import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
@@ -70,6 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -100,7 +100,7 @@
      * scheduled for a package/user.
      */
     @GuardedBy("mLock")
-    private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>();
+    private final ArraySet<Integer> mIsPackageSyncsScheduled = new ArraySet<>();
 
     public PermissionPolicyService(@NonNull Context context) {
         super(context);
@@ -125,10 +125,8 @@
 
             @Override
             public void onPackageChanged(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-
-                if (isStarted(userId)) {
-                    synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+                if (isStarted(UserHandle.getUserId(uid))) {
+                    synchronizePackagePermissionsAndAppOpsForUser(uid);
                 }
             }
 
@@ -139,12 +137,21 @@
         });
 
         permManagerInternal.addOnRuntimePermissionStateChangedListener(
-                this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
+                (packageName, userId) -> {
+                    int uid;
+                    try {
+                        uid = getContext().getPackageManager().getPackageUidAsUser(packageName, 0,
+                                userId);
+                    } catch (NameNotFoundException e) {
+                        Slog.e(LOG_TAG, "Cannot synchronize changed package " + packageName, e);
+                        return;
+                    }
+                    synchronizeUidPermissionsAndAppOpsAsync(uid);
+                });
 
         mAppOpsCallback = new IAppOpsCallback.Stub() {
             public void opChanged(int op, int uid, String packageName) {
-                synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
-                        UserHandle.getUserId(uid));
+                synchronizeUidPermissionsAndAppOpsAsync(uid);
             }
         };
 
@@ -194,19 +201,17 @@
         return AppOpsManager.opToSwitch(op);
     }
 
-    private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
-            @UserIdInt int changedUserId) {
-        if (isStarted(changedUserId)) {
+    private void synchronizeUidPermissionsAndAppOpsAsync(int uid) {
+        if (isStarted(UserHandle.getUserId(uid))) {
             synchronized (mLock) {
-                if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
+                if (mIsPackageSyncsScheduled.add(uid)) {
                     FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             PermissionPolicyService
                                     ::synchronizePackagePermissionsAndAppOpsForUser,
-                            this, packageName, changedUserId));
+                            this, uid));
                 } else {
                     if (DEBUG) {
-                        Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId
-                                + " already scheduled");
+                        Slog.v(LOG_TAG, "sync for " + uid + " already scheduled");
                     }
                 }
             }
@@ -335,39 +340,20 @@
     /**
      * Synchronize a single package.
      */
-    private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
-            @UserIdInt int userId) {
+    private void synchronizePackagePermissionsAndAppOpsForUser(int uid) {
         synchronized (mLock) {
-            mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId));
+            mIsPackageSyncsScheduled.remove(uid);
         }
 
         if (DEBUG) {
             Slog.v(LOG_TAG,
-                    "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", "
-                            + userId + ")");
+                    "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")");
         }
 
-        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
-                PackageManagerInternal.class);
-        final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0,
-                Process.SYSTEM_UID, userId);
-        if (pkg == null) {
-            return;
-        }
         final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
-                getUserContext(getContext(), UserHandle.of(userId)));
-        synchroniser.addPackage(pkg.packageName);
-        final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage(
-                pkg.packageName, userId);
-
-        for (String sharedPkgName : sharedPkgNames) {
-            final AndroidPackage sharedPkg = packageManagerInternal
-                    .getPackage(sharedPkgName);
-            if (sharedPkg != null) {
-                synchroniser.addPackage(sharedPkg.getPackageName());
-            }
-        }
-        synchroniser.syncPackages();
+                getUserContext(getContext(), UserHandle.getUserHandleForUid(uid)));
+        synchroniser.addUid(uid);
+        synchroniser.syncUids();
     }
 
     /**
@@ -381,8 +367,8 @@
         final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
                 getUserContext(getContext(), UserHandle.of(userId)));
         packageManagerInternal.forEachPackage(
-                (pkg) -> synchronizer.addPackage(pkg.getPackageName()));
-        synchronizer.syncPackages();
+                (pkg) -> synchronizer.addUid(pkg.getUid()));
+        synchronizer.syncUids();
     }
 
     /**
@@ -397,37 +383,51 @@
 
         private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos;
 
+        // Cache uid -> packageNames
+        private SparseArray<String[]> mUidToPkg = new SparseArray<>();
+
         /**
          * All ops that need to be flipped to allow.
          *
-         * @see #syncPackages
+         * @see #syncUids
          */
-        private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();
+        private final @NonNull ArraySet<OpToChange> mOpsToAllow = new ArraySet<>();
 
         /**
          * All ops that need to be flipped to ignore.
          *
-         * @see #syncPackages
+         * @see #syncUids
          */
-        private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();
+        private final @NonNull ArraySet<OpToChange> mOpsToIgnore = new ArraySet<>();
 
         /**
          * All ops that need to be flipped to ignore if not allowed.
          *
          * Currently, only used by soft restricted permissions logic.
          *
-         * @see #syncPackages
+         * @see #syncUids
          */
-        private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();
+        private final @NonNull ArraySet<OpToChange> mOpsToIgnoreIfNotAllowed = new ArraySet<>();
 
         /**
          * All ops that need to be flipped to foreground.
          *
          * Currently, only used by the foreground/background permissions logic.
          *
-         * @see #syncPackages
+         * @see #syncUids
          */
-        private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>();
+        private final @NonNull ArraySet<OpToChange> mOpsToForeground = new ArraySet<>();
+
+        private @Nullable String[] getPackageNamesForUid(int uid) {
+            String[] pkgs = mUidToPkg.get(uid);
+            if (pkgs != null) {
+                return pkgs;
+            }
+
+            pkgs = mPackageManager.getPackagesForUid(uid);
+            mUidToPkg.put(uid, pkgs);
+            return pkgs;
+        }
 
         PermissionToOpSynchroniser(@NonNull Context context) {
             mContext = context;
@@ -449,11 +449,11 @@
         }
 
         /**
-         * Set app ops that were added in {@link #addPackage}.
+         * Set app ops that were added in {@link #addUid}.
          *
          * <p>This processes ops previously added by {@link #addAppOps(PackageInfo, String)}
          */
-        private void syncPackages() {
+        private void syncUids() {
             // Remember which ops were already set. This makes sure that we always set the most
             // permissive mode if two OpChanges are scheduled. This can e.g. happen if two
             // permissions change the same op. See {@link #getSwitchOp}.
@@ -461,42 +461,42 @@
 
             final int allowCount = mOpsToAllow.size();
             for (int i = 0; i < allowCount; i++) {
-                final OpToChange op = mOpsToAllow.get(i);
+                final OpToChange op = mOpsToAllow.valueAt(i);
 
-                setUidModeAllowed(op.code, op.uid, op.packageName);
+                setUidModeAllowed(op.code, op.uid);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int foregroundCount = mOpsToForeground.size();
             for (int i = 0; i < foregroundCount; i++) {
-                final OpToChange op = mOpsToForeground.get(i);
+                final OpToChange op = mOpsToForeground.valueAt(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                setUidModeForeground(op.code, op.uid, op.packageName);
+                setUidModeForeground(op.code, op.uid);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int ignoreCount = mOpsToIgnore.size();
             for (int i = 0; i < ignoreCount; i++) {
-                final OpToChange op = mOpsToIgnore.get(i);
+                final OpToChange op = mOpsToIgnore.valueAt(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                setUidModeIgnored(op.code, op.uid, op.packageName);
+                setUidModeIgnored(op.code, op.uid);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
             for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
-                final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
+                final OpToChange op = mOpsToIgnoreIfNotAllowed.valueAt(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
+                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid);
                 if (wasSet) {
                     alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
                 }
@@ -555,7 +555,7 @@
             }
 
             int uid = packageInfo.applicationInfo.uid;
-            OpToChange opToChange = new OpToChange(uid, packageName, appOpCode);
+            OpToChange opToChange = new OpToChange(uid, appOpCode);
             switch (appOpMode) {
                 case MODE_ALLOWED:
                     mOpsToAllow.add(opToChange);
@@ -618,8 +618,7 @@
             }
 
             int uid = packageInfo.applicationInfo.uid;
-            String packageName = packageInfo.packageName;
-            OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode);
+            OpToChange extraOpToChange = new OpToChange(uid, extraOpCode);
             if (policy.mayAllowExtraAppOp()) {
                 mOpsToAllow.add(extraOpToChange);
             } else {
@@ -632,45 +631,56 @@
         }
 
         /**
-         * Add a package for {@link #syncPackages() processing} later.
+         * Add a Uid for {@link #syncUids() processing} later.
          *
          * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
          *
-         * @param pkgName The package to add for later processing.
+         * @param uid The uid to add for later processing.
          */
-        void addPackage(@NonNull String pkgName) {
-            final PackageInfo pkg;
-            try {
-                pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
-            } catch (NameNotFoundException e) {
+        void addUid(int uid) {
+            String[] pkgNames = getPackageNamesForUid(uid);
+            if (pkgNames == null) {
                 return;
             }
 
-            if (pkg.requestedPermissions == null) {
-                return;
-            }
+            for (String pkgName : pkgNames) {
+                final PackageInfo pkg;
+                try {
+                    pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
+                } catch (NameNotFoundException e) {
+                    continue;
+                }
 
-            for (String permission : pkg.requestedPermissions) {
-                addAppOps(pkg, permission);
+                if (pkg.requestedPermissions == null) {
+                    continue;
+                }
+
+                for (String permission : pkg.requestedPermissions) {
+                    addAppOps(pkg, permission);
+                }
             }
         }
 
-        private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
-            setUidMode(opCode, uid, MODE_ALLOWED, packageName);
+        private void setUidModeAllowed(int opCode, int uid) {
+            setUidMode(opCode, uid, MODE_ALLOWED);
         }
 
-        private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
-            setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
+        private void setUidModeForeground(int opCode, int uid) {
+            setUidMode(opCode, uid, MODE_FOREGROUND);
         }
 
-        private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
-            setUidMode(opCode, uid, MODE_IGNORED, packageName);
+        private void setUidModeIgnored(int opCode, int uid) {
+            setUidMode(opCode, uid, MODE_IGNORED);
         }
 
-        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
-                @NonNull String packageName) {
+        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid) {
+            String[] pkgsOfUid = getPackageNamesForUid(uid);
+            if (ArrayUtils.isEmpty(pkgsOfUid)) {
+                return false;
+            }
+
             final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                    opCode), uid, packageName);
+                    opCode), uid, pkgsOfUid[0]);
             if (currentMode != MODE_ALLOWED) {
                 if (currentMode != MODE_IGNORED) {
                     mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, MODE_IGNORED,
@@ -681,20 +691,24 @@
             return false;
         }
 
-        private void setUidMode(int opCode, int uid, int mode,
-                @NonNull String packageName) {
+        private void setUidMode(int opCode, int uid, int mode) {
+            String[] pkgsOfUid = getPackageNamesForUid(uid);
+            if (ArrayUtils.isEmpty(pkgsOfUid)) {
+                return;
+            }
+
             final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                    opCode), uid, packageName);
+                    opCode), uid, pkgsOfUid[0]);
             if (oldMode != mode) {
                 mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, mode,
                         mAppOpsCallback);
                 final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                        opCode), uid, packageName);
+                        opCode), uid, pkgsOfUid[0]);
                 if (newMode != mode) {
                     // Work around incorrectly-set package mode. It never makes sense for app ops
                     // related to runtime permissions, but can get in the way and we have to reset
                     // it.
-                    mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, packageName,
+                    mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, pkgsOfUid[0],
                             AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback);
                 }
             }
@@ -702,14 +716,30 @@
 
         private class OpToChange {
             final int uid;
-            final @NonNull String packageName;
             final int code;
 
-            OpToChange(int uid, @NonNull String packageName, int code) {
+            OpToChange(int uid, int code) {
                 this.uid = uid;
-                this.packageName = packageName;
                 this.code = code;
             }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+
+                OpToChange other = (OpToChange) o;
+                return uid == other.uid && code == other.code;
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(uid, code);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 81ec466..a83c58d 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -27,16 +27,22 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
-import static java.lang.Integer.min;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.Build;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.compat.IPlatformCompat;
 
 /**
  * The behavior of soft restricted permissions is different for each permission. This class collects
@@ -46,6 +52,27 @@
  * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy}
  */
 public abstract class SoftRestrictedPermissionPolicy {
+    /**
+     * Enables scoped storage, with exceptions for apps that explicitly request legacy access, or
+     * apps that hold the {@code android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+     * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+     */
+    @ChangeId
+    // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.P}
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.P)
+    static final long ENABLE_SCOPED_STORAGE = 144914977L;
+
+    /**
+     * Enforces scoped storage for all apps, preventing individual apps from opting out. This change
+     * has precedence over {@code ENABLE_SCOPED_STORAGE}.
+     */
+    @ChangeId
+    // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.Q}.
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
+    static final long REQUIRE_SCOPED_STORAGE = 131432978L;
+
+    private static final String LOG_TAG = SoftRestrictedPermissionPolicy.class.getSimpleName();
+
     private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
                     | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
@@ -60,41 +87,6 @@
             };
 
     /**
-     * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over
-     * what to set, always compute the combined targetSDK.
-     *
-     * @param context A context
-     * @param appInfo The app that is changed
-     * @param user The user the app belongs to
-     *
-     * @return The minimum targetSDK of all apps sharing the uid of the app
-     */
-    private static int getMinimumTargetSDK(@NonNull Context context,
-            @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) {
-        PackageManager pm = context.getPackageManager();
-
-        int minimumTargetSDK = appInfo.targetSdkVersion;
-
-        String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
-        if (uidPkgs != null) {
-            for (String uidPkg : uidPkgs) {
-                if (!uidPkg.equals(appInfo.packageName)) {
-                    ApplicationInfo uidPkgInfo;
-                    try {
-                        uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        continue;
-                    }
-
-                    minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion);
-                }
-            }
-        }
-
-        return minimumTargetSDK;
-    }
-
-    /**
      * Get the policy for a soft restricted permission.
      *
      * @param context A context to use
@@ -114,26 +106,31 @@
             case READ_EXTERNAL_STORAGE: {
                 final boolean isWhiteListed;
                 boolean shouldApplyRestriction;
-                final int targetSDK;
                 final boolean hasRequestedLegacyExternalStorage;
                 final boolean hasWriteMediaStorageGrantedForUid;
+                final boolean isScopedStorageEnabled;
 
                 if (appInfo != null) {
                     PackageManager pm = context.getPackageManager();
                     int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-                    targetSDK = getMinimumTargetSDK(context, appInfo, user);
                     hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
                             appInfo.uid, context);
                     hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid(
                             appInfo.uid, context);
+                    final boolean isScopedStorageRequired =
+                            isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE);
+                    isScopedStorageEnabled =
+                            isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
+                            || isScopedStorageRequired;
+                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
+                            || isScopedStorageRequired;
                 } else {
                     isWhiteListed = false;
                     shouldApplyRestriction = false;
-                    targetSDK = 0;
                     hasRequestedLegacyExternalStorage = false;
                     hasWriteMediaStorageGrantedForUid = false;
+                    isScopedStorageEnabled = false;
                 }
 
                 // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
@@ -143,7 +140,7 @@
                 return new SoftRestrictedPermissionPolicy() {
                     @Override
                     public boolean mayGrantPermission() {
-                        return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+                        return isWhiteListed || isScopedStorageEnabled;
                     }
                     @Override
                     public int getExtraAppOpCode() {
@@ -151,7 +148,7 @@
                     }
                     @Override
                     public boolean mayAllowExtraAppOp() {
-                        return !shouldApplyRestriction && targetSDK <= Build.VERSION_CODES.Q
+                        return !shouldApplyRestriction
                                 && (hasRequestedLegacyExternalStorage
                                         || hasWriteMediaStorageGrantedForUid);
                     }
@@ -163,22 +160,26 @@
             }
             case WRITE_EXTERNAL_STORAGE: {
                 final boolean isWhiteListed;
-                final int targetSDK;
+                final boolean isScopedStorageEnabled;
 
                 if (appInfo != null) {
                     final int flags = context.getPackageManager().getPermissionFlags(permission,
                             appInfo.packageName, user);
                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                    targetSDK = getMinimumTargetSDK(context, appInfo, user);
+                    final boolean isScopedStorageRequired =
+                            isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE);
+                    isScopedStorageEnabled =
+                            isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
+                            || isScopedStorageRequired;
                 } else {
                     isWhiteListed = false;
-                    targetSDK = 0;
+                    isScopedStorageEnabled = false;
                 }
 
                 return new SoftRestrictedPermissionPolicy() {
                     @Override
                     public boolean mayGrantPermission() {
-                        return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+                        return isWhiteListed || isScopedStorageEnabled;
                     }
                 };
             }
@@ -187,6 +188,62 @@
         }
     }
 
+    /**
+     * Checks whether an AppCompat change is enabled for all packages sharing a UID with the
+     * provided application.
+     *
+     * @param context A context to use.
+     * @param appInfo The application for which to check whether the compat change is enabled.
+     * @param user The user the app belongs to.
+     * @param changeId A {@link android.compat.annotation.ChangeId} corresponding to the change.
+     *
+     * @return true if this change is enabled for all apps sharing the UID of the provided app,
+     *         false otherwise.
+     */
+    private static boolean isChangeEnabledForUid(@NonNull Context context,
+            @NonNull ApplicationInfo appInfo, @NonNull UserHandle user, long changeId) {
+        PackageManager pm = context.getPackageManager();
+
+        String[] uidPackages = pm.getPackagesForUid(appInfo.uid);
+        if (uidPackages != null) {
+            for (String uidPackage : uidPackages) {
+                ApplicationInfo uidPackageInfo;
+                try {
+                    uidPackageInfo = pm.getApplicationInfoAsUser(uidPackage, 0, user);
+                } catch (PackageManager.NameNotFoundException e) {
+                    continue;
+                }
+                if (!isChangeEnabled(uidPackageInfo, changeId)) {
+                    // At least one package sharing this UID does not have this change enabled.
+                    return false;
+                }
+            }
+            // All packages sharing this UID returned true for {@link #isChangeEnabled()}.
+            return true;
+        } else {
+            Log.w(LOG_TAG, "Check for change " + changeId + " for uid " + appInfo.uid
+                    + " produced no packages. Defaulting to using the information for "
+                    + appInfo.packageName + " only.");
+            return isChangeEnabled(appInfo, changeId);
+        }
+    }
+
+    private static boolean isChangeEnabled(@NonNull ApplicationInfo appInfo, long changeId) {
+        IBinder binder = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(binder);
+
+        final long callingId = Binder.clearCallingIdentity();
+
+        try {
+            return platformCompat.isChangeEnabled(changeId, appInfo);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Check for change " + changeId + " failed. Defaulting to enabled.", e);
+            return true;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
     private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) {
         PackageManager packageManager = context.getPackageManager();
         String[] packageNames = packageManager.getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index e77839c..524ae54 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -224,6 +224,13 @@
     }
 
     /**
+     * Deletes all device-encrypted apex data snapshots for the given rollback id.
+     */
+    public void destroyApexDeSnapshots(int rollbackId) {
+        mApexManager.destroyDeSnapshots(rollbackId);
+    }
+
+    /**
      * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
      * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
      * of the CE user data snapshot.
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 4d7af9c..5abd9f0 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -44,7 +44,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.pm.ApexManager;
 
 import java.io.File;
 import java.io.IOException;
@@ -182,6 +181,15 @@
     private int mNumPackageSessionsWithSuccess;
 
     /**
+     * A temp flag to facilitate merging of the 2 rollback collections managed by
+     * RollbackManagerServiceImpl. True if this rollback is in the process of enabling and was
+     * originally managed by RollbackManagerServiceImpl#mNewRollbacks.
+     * TODO: remove this flag when merge is completed.
+     */
+    @GuardedBy("mLock")
+    private boolean mIsNewRollback = false;
+
+    /**
      * Constructs a new, empty Rollback instance.
      *
      * @param rollbackId the id of the rollback.
@@ -662,7 +670,7 @@
                 }
             }
             if (containsApex) {
-                ApexManager.getInstance().destroyDeSnapshots(info.getRollbackId());
+                dataHelper.destroyApexDeSnapshots(info.getRollbackId());
             }
 
             RollbackStore.deleteRollback(this);
@@ -829,6 +837,18 @@
         }
     }
 
+    void setIsNewRollback(boolean newRollback) {
+        synchronized (mLock) {
+            mIsNewRollback = newRollback;
+        }
+    }
+
+    boolean isNewRollback() {
+        synchronized (mLock) {
+            return mIsNewRollback;
+        }
+    }
+
     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 8bd9533..1421258 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.AppOpsManager;
@@ -50,7 +51,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.LongArrayQueue;
@@ -121,10 +121,6 @@
     @GuardedBy("mLock")
     private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
 
-    // Rollbacks we are in the process of enabling.
-    @GuardedBy("mLock")
-    private final Set<Rollback> mNewRollbacks = new ArraySet<>();
-
     // The list of all rollbacks, including available and committed rollbacks.
     @GuardedBy("mLock")
     private final List<Rollback> mRollbacks;
@@ -240,17 +236,14 @@
                         Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
                     }
                     synchronized (mLock) {
-                        Rollback found = null;
-                        for (Rollback newRollback : mNewRollbacks) {
-                            if (newRollback.hasToken(token)) {
-                                found = newRollback;
+                        for (int i = 0; i < mRollbacks.size(); ++i) {
+                            Rollback rollback = mRollbacks.get(i);
+                            if (rollback.hasToken(token) && rollback.isEnabling()) {
+                                mRollbacks.remove(i);
+                                rollback.delete(mAppDataRollbackHelper);
                                 break;
                             }
                         }
-                        if (found != null) {
-                            mNewRollbacks.remove(found);
-                            found.delete(mAppDataRollbackHelper);
-                        }
                     }
                 }
             }
@@ -442,15 +435,6 @@
                     rollback.delete(mAppDataRollbackHelper);
                 }
             }
-            Iterator<Rollback> iter2 = mNewRollbacks.iterator();
-            while (iter2.hasNext()) {
-                Rollback newRollback = iter2.next();
-                if (newRollback.includesPackage(packageName)) {
-                    iter2.remove();
-                    newRollback.delete(mAppDataRollbackHelper);
-                }
-
-            }
         }
     }
 
@@ -810,7 +794,8 @@
             newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId());
             if (newRollback == null) {
                 newRollback = createNewRollbackLocked(parentSession);
-                mNewRollbacks.add(newRollback);
+                mRollbacks.add(newRollback);
+                newRollback.setIsNewRollback(true);
             }
         }
         newRollback.addToken(token);
@@ -818,34 +803,6 @@
         return enableRollbackForPackageSession(newRollback, packageSession);
     }
 
-    @WorkerThread
-    private void removeRollbackForPackageSessionId(int sessionId) {
-        if (LOCAL_LOGV) {
-            Slog.v(TAG, "removeRollbackForPackageSessionId=" + sessionId);
-        }
-
-        synchronized (mLock) {
-            Rollback newRollback = getNewRollbackForPackageSessionLocked(sessionId);
-            if (newRollback != null) {
-                Slog.w(TAG, "Delete new rollback id=" + newRollback.info.getRollbackId()
-                        + " for session id=" + sessionId);
-                mNewRollbacks.remove(newRollback);
-                newRollback.delete(mAppDataRollbackHelper);
-            }
-            Iterator<Rollback> iter = mRollbacks.iterator();
-            while (iter.hasNext()) {
-                Rollback rollback = iter.next();
-                if (rollback.getStagedSessionId() == sessionId) {
-                    Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId()
-                            + " for session id=" + sessionId);
-                    iter.remove();
-                    rollback.delete(mAppDataRollbackHelper);
-                    break;
-                }
-            }
-        }
-    }
-
     /**
      * Do code and userdata backups to enable rollback of the given session.
      * In case of multiPackage sessions, <code>session</code> should be one of
@@ -966,15 +923,10 @@
                     + " users=" + Arrays.toString(userIds));
         }
         synchronized (mLock) {
-            // staged installs
             for (int i = 0; i < mRollbacks.size(); i++) {
                 Rollback rollback = mRollbacks.get(i);
                 rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
             }
-            // non-staged installs
-            for (Rollback rollback : mNewRollbacks) {
-                rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
-            }
         }
     }
 
@@ -1200,7 +1152,8 @@
                 synchronized (mLock) {
                     newRollback = getNewRollbackForPackageSessionLocked(sessionId);
                     if (newRollback != null && newRollback.notifySessionWithSuccess()) {
-                        mNewRollbacks.remove(newRollback);
+                        mRollbacks.remove(newRollback);
+                        newRollback.setIsNewRollback(false);
                     } else {
                         // Not all child sessions finished with success.
                         // Don't enable the rollback yet.
@@ -1215,7 +1168,15 @@
                     }
                 }
             } else {
-                removeRollbackForPackageSessionId(sessionId);
+                synchronized (mLock) {
+                    Rollback rollback = getRollbackForSessionLocked(sessionId);
+                    if (rollback != null && rollback.isEnabling()) {
+                        Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId()
+                                + " for failed session id=" + sessionId);
+                        mRollbacks.remove(rollback);
+                        rollback.delete(mAppDataRollbackHelper);
+                    }
+                }
             }
         }
     }
@@ -1376,6 +1337,25 @@
     }
 
     /**
+     * Returns the Rollback associated with the given session if parent or child session id matches.
+     * Returns null if not found.
+     */
+    @WorkerThread
+    @GuardedBy("mLock")
+    @Nullable
+    private Rollback getRollbackForSessionLocked(int sessionId) {
+        // We expect mRollbacks to be a very small list; linear search should be plenty fast.
+        for (int i = 0; i < mRollbacks.size(); ++i) {
+            Rollback rollback = mRollbacks.get(i);
+            if (rollback.getStagedSessionId() == sessionId
+                    || rollback.containsSessionId(sessionId)) {
+                return rollback;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the NewRollback associated with the given package session.
      * Returns null if no NewRollback is found for the given package
      * session.
@@ -1383,11 +1363,11 @@
     @WorkerThread
     @GuardedBy("mLock")
     Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
-        // We expect mNewRollbacks to be a very small list; linear search
+        // We expect mRollbacks to be a very small list; linear search
         // should be plenty fast.
-        for (Rollback newRollback: mNewRollbacks) {
-            if (newRollback.containsSessionId(packageSessionId)) {
-                return newRollback;
+        for (Rollback rollback: mRollbacks) {
+            if (rollback.isNewRollback() && rollback.containsSessionId(packageSessionId)) {
+                return rollback;
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 46ec2f8..1be6f22 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -20,7 +20,12 @@
 import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
 import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
 import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
 import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,7 +38,6 @@
 import android.content.rollback.RollbackInfo;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
@@ -58,8 +62,8 @@
     private static String getLoggingParentName(Context context, @NonNull String packageName) {
         PackageManager packageManager = context.getPackageManager();
         try {
-            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
-                    PackageManager.GET_META_DATA);
+            int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
+            ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
             if (ai.metaData == null) {
                 return null;
             }
@@ -95,6 +99,22 @@
         return loggingParent;
     }
 
+
+    /**
+     * Gets the set of parent packages for a given set of failed package names. In the case that
+     * multiple sessions have failed, we want to log failure for each of the parent packages.
+     * Even if multiple failed packages have the same parent, we only log the parent package once.
+     */
+    private static Set<VersionedPackage> getLogPackages(Context context,
+            @NonNull List<String> failedPackageNames) {
+        Set<VersionedPackage> parentPackages = new ArraySet<>();
+        for (String failedPackageName: failedPackageNames) {
+            parentPackages.add(getLogPackage(context, new VersionedPackage(failedPackageName, 0)));
+        }
+        return parentPackages;
+    }
+
+
     static void logRollbackStatusOnBoot(Context context, int rollbackId,
             List<RollbackInfo> recentlyCommittedRollbacks) {
         PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
@@ -142,19 +162,36 @@
         for (VersionedPackage oldLoggingPackage : oldLoggingPackages) {
             if (sessionInfo.isStagedSessionApplied()) {
                 logEvent(oldLoggingPackage,
-                        FrameworkStatsLog
-                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+                        WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                         WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
             } else if (sessionInfo.isStagedSessionFailed()) {
                 logEvent(oldLoggingPackage,
-                        FrameworkStatsLog
-                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+                        WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
             }
         }
     }
 
     /**
+     * Logs that one or more apexd reverts have occurred, along with the crashing native process
+     * that caused apexd to revert during boot.
+     *
+     * @param context the context to use when determining the log packages
+     * @param failedPackageNames a list of names of packages which were reverted
+     * @param failingNativeProcess the crashing native process which caused a revert
+     */
+    public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames,
+            @NonNull String failingNativeProcess) {
+        Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames);
+        for (VersionedPackage logPackage: logPackages) {
+            logEvent(logPackage,
+                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT,
+                    failingNativeProcess);
+        }
+    }
+
+    /**
      * Log a Watchdog rollback event to statsd.
      *
      * @param logPackage the package to associate the rollback with.
@@ -169,12 +206,25 @@
                 + " rollbackReason: " + rollbackReasonToString(rollbackReason)
                 + " failedPackageName: " + failingPackageName);
         if (logPackage != null) {
-            StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(),
-                    logPackage.getVersionCode(), rollbackReason, failingPackageName);
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                    type,
+                    logPackage.getPackageName(),
+                    logPackage.getVersionCode(),
+                    rollbackReason,
+                    failingPackageName,
+                    new byte[]{});
         } else {
             // In the case that the log package is null, still log an empty string as an
             // indication that retrieving the logging parent failed.
-            StatsLog.logWatchdogRollbackOccurred(type, "", 0, rollbackReason, failingPackageName);
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                    type,
+                    "",
+                    0,
+                    rollbackReason,
+                    failingPackageName,
+                    new byte[]{});
         }
     }
 
@@ -196,14 +246,13 @@
 
     private static String rollbackTypeToString(int type) {
         switch (type) {
-            case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
                 return "ROLLBACK_INITIATE";
-            case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
                 return "ROLLBACK_SUCCESS";
-            case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
                 return "ROLLBACK_FAILURE";
-            case FrameworkStatsLog
-                        .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
                 return "ROLLBACK_BOOT_TRIGGERED";
             default:
                 return "UNKNOWN";
@@ -212,16 +261,16 @@
 
     private static String rollbackReasonToString(int reason) {
         switch (reason) {
-            case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
                 return "REASON_NATIVE_CRASH";
-            case FrameworkStatsLog
-                    .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
                 return "REASON_EXPLICIT_HEALTH_CHECK";
-            case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
                 return "REASON_APP_CRASH";
-            case FrameworkStatsLog
-                        .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
                 return "REASON_APP_NOT_RESPONDING";
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
+                return "REASON_NATIVE_CRASH_DURING_BOOT";
             default:
                 return "UNKNOWN";
         }
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/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..88de340 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,10 +86,16 @@
      */
     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). */
-    private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+    /**
+     * The number of suggestions to keep. These are logged in bug reports to assist when debugging
+     * issues with detection.
+     */
+    private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
 
-    // A log for changes made to the system clock and why.
+    /**
+     * A log that records the decisions / decision metadata that affected the device's system clock
+     * time. This is logged in bug reports to assist with debugging issues with detection.
+     */
     @NonNull
     private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
 
@@ -106,7 +115,7 @@
      * stable.
      */
     @GuardedBy("this")
-    private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex =
+    private final ArrayMapWithHistory<Integer, TelephonyTimeSuggestion> mSuggestionBySlotIndex =
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     @GuardedBy("this")
@@ -135,7 +144,18 @@
         if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
             return;
         }
-        mLastNetworkSuggestion.set(timeSuggestion);
+
+        // The caller submits suggestions with the best available information when there are network
+        // changes. The best available information may have been cached and if they were all stored
+        // this would lead to duplicates showing up in the suggestion history. The suggestions may
+        // be made for different reasons but there is not a significant benefit to storing the same
+        // suggestion information again. doAutoTimeDetection() should still be called: this ensures
+        // the suggestion and device state are always re-evaluated, which might produce a different
+        // detected time if, for example, the age of all suggestions are considered.
+        NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get();
+        if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) {
+            mLastNetworkSuggestion.set(timeSuggestion);
+        }
 
         // Now perform auto time detection. The new suggestion may be used to modify the system
         // clock.
@@ -144,7 +164,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 +177,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 +221,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 +236,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 +246,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 +262,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 +303,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 +326,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 +356,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 +366,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 +383,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 +406,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 +415,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 +559,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 +583,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..cc33fb0
--- /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 suggestions to keep. These are logged in bug reports to assist when debugging
+     * issues with detection.
+     */
+    private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
+
+    @NonNull
+    private final Callback mCallback;
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's time zone.
+     * This is logged in bug reports to assist with debugging issues with detection.
+     */
+    @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_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/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/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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 0418afaf..7986659 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -240,6 +240,10 @@
             target = target.getWindow().getImeControlTarget();
         }
 
+        if (mWin != null && mWin.getSurfaceControl() == null) {
+            // if window doesn't have a surface, set it null and return.
+            setWindow(null, null, null);
+        }
         if (mWin == null) {
             mControlTarget = target;
             return;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2fdcbc90..2196d89 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3323,6 +3323,14 @@
      * @return {@code true} if the top activity looks like it belongs to {@param userId}.
      */
     private void taskTopActivityIsUser(Task task, @UserIdInt int userId) {
+        // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking
+        //  leaf tasks here.
+        if (!task.isLeafTask()) {
+            // No op if not a leaf task since we don't want to report root tasks to
+            // TaskStackListeners.
+            return;
+        }
+
         // To handle the case that work app is in the task but just is not the top one.
         final ActivityRecord activityRecord = task.getTopNonFinishingActivity();
         final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 87c91ef..302e759 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -79,11 +79,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS;
-import static com.android.server.wm.TaskProto.FILLS_PARENT;
-import static com.android.server.wm.TaskProto.SURFACE_HEIGHT;
-import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
-import static com.android.server.wm.TaskProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
@@ -125,7 +120,6 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationTarget;
@@ -1433,18 +1427,27 @@
     }
 
     /**
-     * @return whether or not there are ONLY task overlay activities in the stack.
+     * @return whether or not there are ONLY task overlay activities in the task.
      *         If {@param includeFinishing} is set, then don't ignore finishing activities in the
      *         check. If there are no task overlay activities, this call returns false.
      */
     boolean onlyHasTaskOverlayActivities(boolean includeFinishing) {
-        if (getChildCount() == 0) {
-            return false;
+        int count = 0;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final ActivityRecord r = getChildAt(i).asActivityRecord();
+            if (r == null) {
+                // Has a child that is other than Activity.
+                return false;
+            }
+            if (!includeFinishing && r.finishing) {
+                continue;
+            }
+            if (!r.isTaskOverlay()) {
+                return false;
+            }
+            count++;
         }
-        if (includeFinishing) {
-            return getActivity((r) -> r.isTaskOverlay()) != null;
-        }
-        return getActivity((r) -> !r.finishing && r.isTaskOverlay()) != null;
+        return count > 0;
     }
 
     private boolean autoRemoveFromRecents() {
@@ -2371,6 +2374,15 @@
         return getRootTask() == this;
     }
 
+    boolean isLeafTask() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            if (mChildren.get(i).asTask() != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     int getDescendantTaskCount() {
         final int[] currentCount = {0};
         final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
@@ -3195,12 +3207,16 @@
         info.lastActiveTime = lastActiveTime;
         info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription());
         info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode();
-        info.resizeMode = mResizeMode;
         info.configuration.setTo(getConfiguration());
         info.token = mRemoteToken;
         // Get's the first non-undefined activity type among this and children. Can't use
         // configuration.windowConfiguration because that would only be this level.
         info.topActivityType = getActivityType();
+
+        //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
+        //                    order changes.
+        final Task top = getTopMostTask();
+        info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 44a6fc9..0a0530c9 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -23,6 +23,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -38,6 +40,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;
@@ -46,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.WeakHashMap;
 
@@ -53,7 +57,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 +79,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 +93,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 +184,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 +202,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 +229,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);
     }
 
@@ -350,6 +378,45 @@
         }
     }
 
+    @Override
+    public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) {
+        enforceStackPermission("getChildTasks()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                if (parent == null) {
+                    throw new IllegalArgumentException("Can't get children of null parent");
+                }
+                final WindowContainer container = WindowContainer.fromBinder(parent.asBinder());
+                if (container == null) {
+                    Slog.e(TAG, "Can't get children of " + parent + " because it is not valid.");
+                    return null;
+                }
+                // For now, only support returning children of persistent root tasks (of which the
+                // only current implementation is TaskTile).
+                if (!(container instanceof TaskTile)) {
+                    Slog.w(TAG, "Can only get children of root tasks created via createRootTask");
+                    return null;
+                }
+                ArrayList<RunningTaskInfo> out = new ArrayList<>();
+                // Tiles aren't real parents, so we need to go through stacks on the display to
+                // ensure correct ordering.
+                final DisplayContent dc = container.getDisplayContent();
+                for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+                    final ActivityStack as = dc.getStackAt(i);
+                    if (as.getTile() == container) {
+                        final RunningTaskInfo info = new RunningTaskInfo();
+                        as.fillTaskInfo(info);
+                        out.add(info);
+                    }
+                }
+                return out;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int sanitizeAndApplyChange(WindowContainer container,
             WindowContainerTransaction.Change change) {
         if (!(container instanceof Task)) {
@@ -380,6 +447,54 @@
         return effects;
     }
 
+    private int sanitizeAndApplyHierarchyOp(WindowContainer container,
+            WindowContainerTransaction.HierarchyOp hop) {
+        if (!(container instanceof Task)) {
+            throw new IllegalArgumentException("Invalid container in hierarchy op");
+        }
+        if (hop.isReparent()) {
+            // special case for tiles since they are "virtual" parents
+            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+                ActivityStack as = (ActivityStack) container;
+                TaskTile newParent = hop.getNewParent() == null ? null
+                        : (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
+                if (as.getTile() != newParent) {
+                    if (as.getTile() != null) {
+                        as.getTile().removeChild(as);
+                    }
+                    if (newParent != null) {
+                        if (!as.affectedBySplitScreenResize()) {
+                            return 0;
+                        }
+                        newParent.addChild(as, POSITION_TOP);
+                    }
+                }
+                if (hop.getToTop()) {
+                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+                } else {
+                    as.getDisplay().positionStackAtBottom(as);
+                }
+            } else if (container instanceof Task) {
+                throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
+            }
+        } else {
+            // Ugh, of course ActivityStack has its own special reorder logic...
+            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+                ActivityStack as = (ActivityStack) container;
+                if (hop.getToTop()) {
+                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+                } else {
+                    as.getDisplay().positionStackAtBottom(as);
+                }
+            } else {
+                container.getParent().positionChildAt(
+                        hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+                        container, false /* includingParents */);
+            }
+        }
+        return TRANSACT_EFFECTS_LIFECYCLE;
+    }
+
     private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
             int windowMask, Configuration config) {
         if ((container instanceof ActivityStack)
@@ -408,15 +523,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<>();
@@ -425,15 +560,25 @@
                     while (entries.hasNext()) {
                         final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
                                 entries.next();
-                        final WindowContainer wc = WindowContainer.RemoteToken.fromBinder(
-                                entry.getKey()).getContainer();
+                        final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
                         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);
+                        }
+                    }
+                    // Hierarchy changes
+                    final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
+                    for (int i = 0, n = hops.size(); i < n; ++i) {
+                        final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                        effects |= sanitizeAndApplyHierarchyOp(wc, hop);
                     }
                     if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
                         // Already calls ensureActivityConfig
@@ -454,10 +599,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/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 8bbb0d7..b5892b9 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -20,6 +20,11 @@
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
@@ -29,7 +34,6 @@
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.IActivityTaskManager;
 import android.graphics.Point;
@@ -55,11 +59,10 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TaskResizingAlgorithm;
+import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
 import com.android.server.protolog.common.ProtoLog;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 class TaskPositioner implements IBinder.DeathRecipient {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     private static final String TAG_LOCAL = "TaskPositioner";
@@ -67,33 +70,10 @@
 
     private static Factory sFactory;
 
-    @IntDef(flag = true,
-            value = {
-                    CTRL_NONE,
-                    CTRL_LEFT,
-                    CTRL_RIGHT,
-                    CTRL_TOP,
-                    CTRL_BOTTOM
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface CtrlType {}
-
-    private static final int CTRL_NONE   = 0x0;
-    private static final int CTRL_LEFT   = 0x1;
-    private static final int CTRL_RIGHT  = 0x2;
-    private static final int CTRL_TOP    = 0x4;
-    private static final int CTRL_BOTTOM = 0x8;
-
     public static final float RESIZING_HINT_ALPHA = 0.5f;
 
     public static final int RESIZING_HINT_DURATION_MS = 0;
 
-    // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
-    // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
-    // aspect he desires.
-    @VisibleForTesting
-    static final float MIN_ASPECT = 1.2f;
-
     private final WindowManagerService mService;
     private final IActivityTaskManager mActivityManager;
     private WindowPositionerEventReceiver mInputEventReceiver;
@@ -477,122 +457,13 @@
      */
     @VisibleForTesting
     void resizeDrag(float x, float y) {
-        // This is a resizing operation.
-        // We need to keep various constraints:
-        // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
-        // 2. The orientation is kept - if required.
-        final int deltaX = Math.round(x - mStartDragX);
-        final int deltaY = Math.round(y - mStartDragY);
-        int left = mWindowOriginalBounds.left;
-        int top = mWindowOriginalBounds.top;
-        int right = mWindowOriginalBounds.right;
-        int bottom = mWindowOriginalBounds.bottom;
-
-        // Calculate the resulting width and height of the drag operation.
-        int width = right - left;
-        int height = bottom - top;
-        if ((mCtrlType & CTRL_LEFT) != 0) {
-            width = Math.max(mMinVisibleWidth, width - deltaX);
-        } else if ((mCtrlType & CTRL_RIGHT) != 0) {
-            width = Math.max(mMinVisibleWidth, width + deltaX);
-        }
-        if ((mCtrlType & CTRL_TOP) != 0) {
-            height = Math.max(mMinVisibleHeight, height - deltaY);
-        } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
-            height = Math.max(mMinVisibleHeight, height + deltaY);
-        }
-
-        // If we have to preserve the orientation - check that we are doing so.
-        final float aspect = (float) width / (float) height;
-        if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
-                || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
-            // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
-            // drag axis. What ever is producing the bigger rectangle will be chosen.
-            int width1;
-            int width2;
-            int height1;
-            int height2;
-            if (mStartOrientationWasLandscape) {
-                // Assuming that the width is our target we calculate the height.
-                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
-                height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
-                if (height1 < mMinVisibleHeight) {
-                    // If the resulting height is too small we adjust to the minimal size.
-                    height1 = mMinVisibleHeight;
-                    width1 = Math.max(mMinVisibleWidth,
-                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
-                }
-                // Assuming that the height is our target we calculate the width.
-                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
-                width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
-                if (width2 < mMinVisibleWidth) {
-                    // If the resulting width is too small we adjust to the minimal size.
-                    width2 = mMinVisibleWidth;
-                    height2 = Math.max(mMinVisibleHeight,
-                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
-                }
-            } else {
-                // Assuming that the width is our target we calculate the height.
-                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
-                height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
-                if (height1 < mMinVisibleHeight) {
-                    // If the resulting height is too small we adjust to the minimal size.
-                    height1 = mMinVisibleHeight;
-                    width1 = Math.max(mMinVisibleWidth,
-                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
-                }
-                // Assuming that the height is our target we calculate the width.
-                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
-                width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
-                if (width2 < mMinVisibleWidth) {
-                    // If the resulting width is too small we adjust to the minimal size.
-                    width2 = mMinVisibleWidth;
-                    height2 = Math.max(mMinVisibleHeight,
-                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
-                }
-            }
-
-            // Use the bigger of the two rectangles if the major change was positive, otherwise
-            // do the opposite.
-            final boolean grows = width > (right - left) || height > (bottom - top);
-            if (grows == (width1 * height1 > width2 * height2)) {
-                width = width1;
-                height = height1;
-            } else {
-                width = width2;
-                height = height2;
-            }
-        }
-
-        // Update mWindowDragBounds to the new drag size.
-        updateDraggedBounds(left, top, right, bottom, width, height);
+        updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY,
+                mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight,
+                mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape));
     }
 
-    /**
-     * Given the old coordinates and the new width and height, update the mWindowDragBounds.
-     *
-     * @param left      The original left bound before the user started dragging.
-     * @param top       The original top bound before the user started dragging.
-     * @param right     The original right bound before the user started dragging.
-     * @param bottom    The original bottom bound before the user started dragging.
-     * @param newWidth  The new dragged width.
-     * @param newHeight The new dragged height.
-     */
-    void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
-                             int newHeight) {
-        // Generate the final bounds by keeping the opposite drag edge constant.
-        if ((mCtrlType & CTRL_LEFT) != 0) {
-            left = right - newWidth;
-        } else { // Note: The right might have changed - if we pulled at the right or not.
-            right = left + newWidth;
-        }
-        if ((mCtrlType & CTRL_TOP) != 0) {
-            top = bottom - newHeight;
-        } else { // Note: The height might have changed - if we pulled at the bottom or not.
-            bottom = top + newHeight;
-        }
-
-        mWindowDragBounds.set(left, top, right, bottom);
+    private void updateDraggedBounds(Rect newBounds) {
+        mWindowDragBounds.set(newBounds);
 
         checkBoundsForOrientationViolations(mWindowDragBounds);
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8672315..504aa2d 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();
@@ -2285,6 +2296,10 @@
         return mRemoteToken;
     }
 
+    static WindowContainer fromBinder(IBinder binder) {
+        return RemoteToken.fromBinder(binder).getContainer();
+    }
+
     static class RemoteToken extends IWindowContainer.Stub {
         final WeakReference<WindowContainer> mWeakRef;
 
@@ -2316,4 +2331,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..a370093 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) {
@@ -7992,7 +8021,11 @@
             int displayId, Rect outContentInsets, Rect outStableInsets,
             DisplayCutout.ParcelableWrapper displayCutout) {
         synchronized (mGlobalLock) {
-            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
+            if (dc == null) {
+                throw new WindowManager.InvalidDisplayException("Display#" + displayId
+                        + "could not be found!");
+            }
             final WindowToken windowToken = dc.getWindowToken(attrs.token);
             final ActivityRecord activity;
             if (windowToken != null && windowToken.asActivityRecord() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 87b04b2..c7d00ec 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -178,7 +178,7 @@
     private long mLastActivityFinishTime;
 
     // Last configuration that was reported to the process.
-    private final Configuration mLastReportedConfiguration;
+    private final Configuration mLastReportedConfiguration = new Configuration();
     // Configuration that is waiting to be dispatched to the process.
     private Configuration mPendingConfiguration;
     private final Configuration mNewOverrideConfig = new Configuration();
@@ -192,7 +192,7 @@
     /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
     private boolean mRunningRemoteAnimation;
 
-    public WindowProcessController(ActivityTaskManagerService atm, ApplicationInfo info,
+    public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info,
             String name, int uid, int userId, Object owner, WindowProcessListener listener) {
         mInfo = info;
         mName = name;
@@ -201,11 +201,8 @@
         mOwner = owner;
         mListener = listener;
         mAtm = atm;
-        mLastReportedConfiguration = new Configuration();
         mDisplayId = INVALID_DISPLAY;
-        if (atm != null) {
-            onConfigurationChanged(atm.getGlobalConfiguration());
-        }
+        onConfigurationChanged(atm.getGlobalConfiguration());
     }
 
     public void setPid(int pid) {
@@ -220,6 +217,11 @@
     public void setThread(IApplicationThread thread) {
         synchronized (mAtm.mGlobalLockWithoutBoost) {
             mThread = thread;
+            // In general this is called from attaching application, so the last configuration
+            // has been sent to client by {@link android.app.IApplicationThread#bindApplication}.
+            // If this process is system server, it is fine because system is booting and a new
+            // configuration will update when display is ready.
+            setLastReportedConfiguration(getConfiguration());
         }
     }
 
@@ -1060,7 +1062,6 @@
         mNewOverrideConfig.setTo(mergedOverrideConfig);
         mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
         super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig);
-        updateConfiguration();
     }
 
     private void updateConfiguration() {
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..49c7e0a3 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,7 +13,6 @@
     ],
 
     srcs: [
-        ":graphicsstats_proto",
         ":lib_alarmManagerService_native",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
@@ -53,7 +52,6 @@
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_VibratorService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
-        "com_android_server_GraphicsStatsService.cpp",
         "com_android_server_am_CachedAppOptimizer.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_incremental_IncrementalManagerService.cpp",
@@ -107,8 +105,7 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
-        "libprotobuf-cpp-lite",
-        "libprotoutil",
+        "libstatshidl",
         "libstatspull",
         "libstatssocket",
         "libstatslog",
@@ -155,6 +152,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
deleted file mode 100644
index 7644ade..0000000
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2017 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 "GraphicsStatsService"
-
-#include <jni.h>
-#include <log/log.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include <JankTracker.h>
-#include <service/GraphicsStatsService.h>
-#include <stats_pull_atom_callback.h>
-#include <stats_event.h>
-#include <statslog.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <android/util/ProtoOutputStream.h>
-#include "android/graphics/Utils.h"
-#include "core_jni_helpers.h"
-#include "protos/graphicsstats.pb.h"
-#include <cstring>
-#include <memory>
-
-namespace android {
-
-using namespace android::uirenderer;
-
-static jint getAshmemSize(JNIEnv*, jobject) {
-    return sizeof(ProfileData);
-}
-
-static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
-    GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto
-            ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text);
-    return reinterpret_cast<jlong>(dump);
-}
-
-static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
-        jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
-    std::string path;
-    const ProfileData* data = nullptr;
-    LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
-    ScopedByteArrayRO buffer{env};
-    if (jdata != nullptr) {
-        buffer.reset(jdata);
-        LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
-                "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
-        data = reinterpret_cast<const ProfileData*>(buffer.get());
-    }
-    if (jpath != nullptr) {
-        ScopedUtfChars pathChars(env, jpath);
-        LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
-        path.assign(pathChars.c_str(), pathChars.size());
-    }
-    ScopedUtfChars packageChars(env, jpackage);
-    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
-    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
-    LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
-
-    const std::string package(packageChars.c_str(), packageChars.size());
-    GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
-}
-
-static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
-    ScopedUtfChars pathChars(env, jpath);
-    LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
-    const std::string path(pathChars.c_str(), pathChars.size());
-    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
-    GraphicsStatsService::addToDump(dump, path);
-}
-
-static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
-    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
-    GraphicsStatsService::finishDump(dump);
-}
-
-static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
-    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
-    std::vector<uint8_t>* result = new std::vector<uint8_t>();
-    GraphicsStatsService::finishDumpInMemory(dump,
-        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
-            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
-            if (outBuffer->size() < totalSize) {
-                outBuffer->resize(totalSize);
-            }
-            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
-        }, env, result);
-    return reinterpret_cast<jlong>(result);
-}
-
-static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
-        jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
-    ScopedByteArrayRO buffer(env, jdata);
-    LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
-            "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
-    ScopedUtfChars pathChars(env, jpath);
-    LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
-    ScopedUtfChars packageChars(env, jpackage);
-    LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
-
-    const std::string path(pathChars.c_str(), pathChars.size());
-    const std::string package(packageChars.c_str(), packageChars.size());
-    const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
-    GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
-}
-
-static jobject gGraphicsStatsServiceObject = nullptr;
-static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
-
-static JNIEnv* getJNIEnv() {
-    JavaVM* vm = AndroidRuntime::getJavaVM();
-    JNIEnv* env = nullptr;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
-            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
-        }
-    }
-    return env;
-}
-
-using namespace google::protobuf;
-
-// Field ids taken from FrameTimingHistogram message in atoms.proto
-#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
-#define FRAME_COUNTS_FIELD_NUMBER 2
-
-static void writeCpuHistogram(stats_event* event,
-                              const uirenderer::protos::GraphicsStatsProto& stat) {
-    util::ProtoOutputStream proto;
-    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
-        auto& bucket = stat.histogram(bucketIndex);
-        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
-                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
-                    (int)bucket.render_millis());
-    }
-    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
-        auto& bucket = stat.histogram(bucketIndex);
-        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
-                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
-                    (long long)bucket.frame_count());
-    }
-    std::vector<uint8_t> outVector;
-    proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(event, outVector.data(), outVector.size());
-}
-
-static void writeGpuHistogram(stats_event* event,
-                              const uirenderer::protos::GraphicsStatsProto& stat) {
-    util::ProtoOutputStream proto;
-    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
-        auto& bucket = stat.gpu_histogram(bucketIndex);
-        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
-                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
-                    (int)bucket.render_millis());
-    }
-    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
-        auto& bucket = stat.gpu_histogram(bucketIndex);
-        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
-                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
-                    (long long)bucket.frame_count());
-    }
-    std::vector<uint8_t> outVector;
-    proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(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) {
-    JNIEnv* env = getJNIEnv();
-    if (!env) {
-        return false;
-    }
-    if (gGraphicsStatsServiceObject == nullptr) {
-        ALOGE("Failed to get graphicsstats service");
-        return STATS_PULL_SKIP;
-    }
-
-    for (bool lastFullDay : {true, false}) {
-        jlong jdata = (jlong) env->CallLongMethod(
-                    gGraphicsStatsServiceObject,
-                    gGraphicsStatsService_pullGraphicsStatsMethodID,
-                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
-        if (env->ExceptionCheck()) {
-            env->ExceptionDescribe();
-            env->ExceptionClear();
-            ALOGE("Failed to invoke graphicsstats service");
-            return STATS_PULL_SKIP;
-        }
-        if (!jdata) {
-            // null means data is not available for that day.
-            continue;
-        }
-        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
-        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
-        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
-        int dataSize = buffer->size();
-        if (!dataSize) {
-            // Data is not available for that day.
-            continue;
-        }
-        io::ArrayInputStream input{buffer->data(), dataSize};
-        bool success = serviceDump.ParseFromZeroCopyStream(&input);
-        if (!success) {
-            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
-                  serviceDump.InitializationErrorString().c_str(), dataSize);
-            return STATS_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());
-            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);
-        }
-    }
-    return STATS_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);
-}
-
-static void nativeDestructor(JNIEnv* env, jobject javaObject) {
-    //TODO: Unregister the puller callback when a new API is available.
-    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
-    gGraphicsStatsServiceObject = nullptr;
-}
-
-static const JNINativeMethod sMethods[] = {
-    { "nGetAshmemSize", "()I", (void*) getAshmemSize },
-    { "nCreateDump", "(IZ)J", (void*) createDump },
-    { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
-    { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
-    { "nFinishDump", "(J)V", (void*) finishDump },
-    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
-    { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
-    { "nativeInit", "()V", (void*) nativeInit },
-    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
-};
-
-int register_android_server_GraphicsStatsService(JNIEnv* env)
-{
-    jclass graphicsStatsService_class = FindClassOrDie(env,
-            "com/android/server/GraphicsStatsService");
-    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
-            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
-    return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
-                                    sMethods, NELEM(sMethods));
-}
-
-} // namespace android
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/onload.cpp b/services/core/jni/onload.cpp
index 1202ad3..c186494 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,7 +49,7 @@
 int register_android_server_Watchdog(JNIEnv* env);
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
-int register_android_server_GraphicsStatsService(JNIEnv* env);
+int register_android_graphics_GraphicsStatsService(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
@@ -102,7 +102,7 @@
     register_android_server_HardwarePropertiesManagerService(env);
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
-    register_android_server_GraphicsStatsService(env);
+    register_android_graphics_GraphicsStatsService(env);
     register_android_hardware_display_DisplayViewport(env);
     register_android_server_net_NetworkStatsFactory(env);
     register_android_server_net_NetworkStatsService(env);
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 2636586..109f39d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -458,8 +458,6 @@
     // A collection of user restrictions that are deprecated and should simply be ignored.
     private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
     private static final String AB_DEVICE_KEY = "ro.build.ab_update";
-    // Permissions related to location which must not be granted automatically
-    private static  final Set<String> LOCATION_PERMISSIONS;
 
     static {
         SECURE_SETTINGS_WHITELIST = new ArraySet<>();
@@ -504,11 +502,6 @@
         DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
                 UserManager.DISALLOW_ADD_MANAGED_PROFILE,
                 UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
-
-        LOCATION_PERMISSIONS = Sets.newHashSet(
-                permission.ACCESS_FINE_LOCATION,
-                permission.ACCESS_BACKGROUND_LOCATION,
-                permission.ACCESS_COARSE_LOCATION);
     }
 
     /**
@@ -4680,12 +4673,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
@@ -8517,6 +8513,9 @@
     }
 
     private void clearOverrideApnUnchecked() {
+        if (!mHasTelephonyFeature) {
+            return;
+        }
         // Disable Override APNs and remove them from database.
         setOverrideApnsEnabledUnchecked(false);
         final List<ApnSetting> apns = getOverrideApnsUnchecked();
@@ -11595,6 +11594,11 @@
                 millis, "DevicePolicyManagerService: setTime");
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getTimeDetector().suggestManualTime(manualTimeSuggestion));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_TIME)
+                .setAdmin(who)
+                .write();
         return true;
     }
 
@@ -11611,6 +11615,11 @@
                         timeZone, "DevicePolicyManagerService: setTimeZone");
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.getTimeZoneDetector().suggestManualTimeZone(manualTimeZoneSuggestion));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_TIME_ZONE)
+                .setAdmin(who)
+                .write();
         return true;
     }
 
@@ -12528,14 +12537,6 @@
                             true);
                 }
 
-                // Prevent granting location-related permissions without user consent.
-                if (LOCATION_PERMISSIONS.contains(permission)
-                        && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
-                        && !isUnattendedManagedKioskUnchecked()) {
-                    callback.sendResult(null);
-                    return;
-                }
-
                 if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 0941831..b2c316a 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -16,6 +16,7 @@
 
 #include "BinderIncrementalService.h"
 
+#include <android-base/logging.h>
 #include <binder/IResultReceiver.h>
 #include <binder/PermissionCache.h>
 #include <incfs.h>
@@ -24,7 +25,6 @@
 #include "jni.h"
 #include "nativehelper/JNIHelp.h"
 #include "path.h"
-#include <android-base/logging.h>
 
 using namespace std::literals;
 using namespace android::incremental;
@@ -277,6 +277,13 @@
     return ok();
 }
 
+binder::Status BinderIncrementalService::configureNativeBinaries(
+        int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath,
+        const std::string& abi, bool* _aidl_return) {
+    *_aidl_return = mImpl.configureNativeBinaries(storageId, apkFullPath, libDirRelativePath, abi);
+    return ok();
+}
+
 } // namespace android::os::incremental
 
 jlong Incremental_IncrementalService_Start() {
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 8a09977..51d7de3 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -28,11 +28,11 @@
 class BinderIncrementalService : public BnIncrementalService,
                                  public BinderService<BinderIncrementalService> {
 public:
-    BinderIncrementalService(const sp<IServiceManager> &sm);
+    BinderIncrementalService(const sp<IServiceManager>& sm);
 
-    static BinderIncrementalService *start();
-    static const char16_t *getServiceName() { return u"incremental_service"; }
-    status_t dump(int fd, const Vector<String16> &args) final;
+    static BinderIncrementalService* start();
+    static const char16_t* getServiceName() { return u"incremental_service"; }
+    status_t dump(int fd, const Vector<String16>& args) final;
 
     void onSystemReady();
     void onInvalidStorage(int mountId);
@@ -70,6 +70,9 @@
                                    std::vector<uint8_t>* _aidl_return) final;
     binder::Status startLoading(int32_t storageId, bool* _aidl_return) final;
     binder::Status deleteStorage(int32_t storageId) final;
+    binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
+                                           const std::string& libDirRelativePath,
+                                           const std::string& abi, bool* _aidl_return) final;
 
 private:
     android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index dbd97cf..3b51377 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -60,6 +60,9 @@
     static constexpr auto storagePrefix = "st"sv;
     static constexpr auto mountpointMdPrefix = ".mountpoint."sv;
     static constexpr auto infoMdName = ".info"sv;
+    static constexpr auto libDir = "lib"sv;
+    static constexpr auto libSuffix = ".so"sv;
+    static constexpr auto blockSize = 4096;
 };
 
 static const Constants& constants() {
@@ -259,16 +262,18 @@
 
 inline const char* toString(TimePoint t) {
     using SystemClock = std::chrono::system_clock;
-    time_t time = SystemClock::to_time_t(SystemClock::now() + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now()));
+    time_t time = SystemClock::to_time_t(
+            SystemClock::now() +
+            std::chrono::duration_cast<SystemClock::duration>(t - Clock::now()));
     return std::ctime(&time);
 }
 
 inline const char* toString(IncrementalService::BindKind kind) {
     switch (kind) {
-    case IncrementalService::BindKind::Temporary:
-        return "Temporary";
-    case IncrementalService::BindKind::Permanent:
-        return "Permanent";
+        case IncrementalService::BindKind::Temporary:
+            return "Temporary";
+        case IncrementalService::BindKind::Permanent:
+            return "Permanent";
     }
 }
 
@@ -1124,6 +1129,122 @@
     return true;
 }
 
+// Extract lib filse from zip, create new files in incfs and write data to them
+bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
+                                                 std::string_view libDirRelativePath,
+                                                 std::string_view abi) {
+    const auto ifs = getIfs(storage);
+    // First prepare target directories if they don't exist yet
+    if (auto res = makeDirs(storage, libDirRelativePath, 0755)) {
+        LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath
+                   << " errno: " << res;
+        return false;
+    }
+
+    std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data()));
+    if (!zipFile) {
+        LOG(ERROR) << "Failed to open zip file at " << apkFullPath;
+        return false;
+    }
+    void* cookie = nullptr;
+    const auto libFilePrefix = path::join(constants().libDir, abi);
+    if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */,
+                                       constants().libSuffix.data() /* suffix */)) {
+        LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath;
+        return false;
+    }
+    ZipEntryRO entry = nullptr;
+    bool success = true;
+    while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) {
+        char fileName[PATH_MAX];
+        if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) {
+            continue;
+        }
+        const auto libName = path::basename(fileName);
+        const auto targetLibPath = path::join(libDirRelativePath, libName);
+        const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath);
+        // If the extract file already exists, skip
+        struct stat st;
+        if (stat(targetLibPathAbsolute.c_str(), &st) == 0) {
+            LOG(INFO) << "Native lib file already exists: " << targetLibPath
+                      << "; skipping extraction";
+            continue;
+        }
+
+        uint32_t uncompressedLen;
+        if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr,
+                                         nullptr, nullptr)) {
+            LOG(ERROR) << "Failed to read native lib entry: " << fileName;
+            success = false;
+            break;
+        }
+
+        // Create new lib file without signature info
+        incfs::NewFileParams libFileParams;
+        libFileParams.size = uncompressedLen;
+        libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
+        // Metadata of the new lib file is its relative path
+        IncFsSpan libFileMetadata;
+        libFileMetadata.data = targetLibPath.c_str();
+        libFileMetadata.size = targetLibPath.size();
+        libFileParams.metadata = libFileMetadata;
+        incfs::FileId libFileId = idFromMetadata(targetLibPath);
+        if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) {
+            LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res;
+            success = false;
+            // If one lib file fails to be created, abort others as well
+            break;
+        }
+
+        // Write extracted data to new file
+        std::vector<uint8_t> libData(uncompressedLen);
+        if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) {
+            LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName;
+            success = false;
+            break;
+        }
+        android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId));
+        if (writeFd < 0) {
+            LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
+            success = false;
+            break;
+        }
+        const int numBlocks = uncompressedLen / constants().blockSize + 1;
+        std::vector<IncFsDataBlock> instructions;
+        auto remainingData = std::span(libData);
+        for (int i = 0; i < numBlocks - 1; i++) {
+            auto inst = IncFsDataBlock{
+                    .fileFd = writeFd,
+                    .pageIndex = static_cast<IncFsBlockIndex>(i),
+                    .compression = INCFS_COMPRESSION_KIND_NONE,
+                    .kind = INCFS_BLOCK_KIND_DATA,
+                    .dataSize = static_cast<uint16_t>(constants().blockSize),
+                    .data = reinterpret_cast<const char*>(remainingData.data()),
+            };
+            instructions.push_back(inst);
+            remainingData = remainingData.subspan(constants().blockSize);
+        }
+        // Last block
+        auto inst = IncFsDataBlock{
+                .fileFd = writeFd,
+                .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1),
+                .compression = INCFS_COMPRESSION_KIND_NONE,
+                .kind = INCFS_BLOCK_KIND_DATA,
+                .dataSize = static_cast<uint16_t>(remainingData.size()),
+                .data = reinterpret_cast<const char*>(remainingData.data()),
+        };
+        instructions.push_back(inst);
+        size_t res = mIncFs->writeBlocks(instructions);
+        if (res != instructions.size()) {
+            LOG(ERROR) << "Failed to write data into: " << targetLibPath;
+            success = false;
+        }
+        instructions.clear();
+    }
+    zipFile.get()->endIteration(cookie);
+    return success;
+}
+
 binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
                                                                                   int newStatus) {
     std::unique_lock l(incrementalService.mLock);
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index dec9f64..2444dde 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -126,7 +126,8 @@
 
     std::vector<std::string> listFiles(StorageId storage) const;
     bool startLoading(StorageId storage) const;
-
+    bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
+                                 std::string_view libDirRelativePath, std::string_view abi);
     class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
     public:
         IncrementalDataLoaderListener(IncrementalService& incrementalService)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 036335c..eef6c63 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,6 +41,7 @@
 import android.content.res.Resources.Theme;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
+import android.graphics.GraphicsStatsService;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityModuleConnector;
 import android.net.ITetheringConnector;
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/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index bb97533..b60ed3e 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -35,7 +35,7 @@
  */
 public class ConversationInfo {
 
-    private static final int FLAG_VIP = 1;
+    private static final int FLAG_IMPORTANT = 1;
 
     private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1;
 
@@ -50,7 +50,7 @@
     private static final int FLAG_DEMOTED = 1 << 6;
 
     @IntDef(flag = true, prefix = {"FLAG_"}, value = {
-            FLAG_VIP,
+            FLAG_IMPORTANT,
             FLAG_NOTIFICATION_SILENCED,
             FLAG_BUBBLED,
             FLAG_PERSON_IMPORTANT,
@@ -129,9 +129,9 @@
         return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
     }
 
-    /** Whether this conversation is marked as VIP by the user. */
-    public boolean isVip() {
-        return hasConversationFlags(FLAG_VIP);
+    /** Whether this conversation is marked as important by the user. */
+    public boolean isImportant() {
+        return hasConversationFlags(FLAG_IMPORTANT);
     }
 
     /** Whether the notifications for this conversation should be silenced. */
@@ -208,8 +208,8 @@
         sb.append("]");
         sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags));
         sb.append(" [");
-        if (isVip()) {
-            sb.append("Vip");
+        if (isImportant()) {
+            sb.append("Imp");
         }
         if (isNotificationSilenced()) {
             sb.append("Sil");
@@ -221,7 +221,7 @@
             sb.append("Dem");
         }
         if (isPersonImportant()) {
-            sb.append("Imp");
+            sb.append("PIm");
         }
         if (isPersonBot()) {
             sb.append("Bot");
@@ -318,8 +318,8 @@
             return this;
         }
 
-        Builder setVip(boolean value) {
-            return setConversationFlag(FLAG_VIP, value);
+        Builder setImportant(boolean value) {
+            return setConversationFlag(FLAG_IMPORTANT, value);
         }
 
         Builder setNotificationSilenced(boolean value) {
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..7a3ed53 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -21,11 +21,11 @@
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 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 +69,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 +97,6 @@
     private final ContentObserver mMmsSmsContentObserver;
 
     private ShortcutServiceInternal mShortcutServiceInternal;
-    private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private ShortcutManager mShortcutManager;
     private UserManager mUserManager;
 
@@ -118,7 +118,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);
 
@@ -160,8 +159,8 @@
         mNotificationListeners.put(userId, notificationListener);
         try {
             notificationListener.registerAsSystemService(mContext,
-                    new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getSimpleName()),
-                    UserHandle.myUserId());
+                    new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getCanonicalName()),
+                    userId);
         } catch (RemoteException e) {
             // Should never occur for local calls.
         }
@@ -293,13 +292,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 +385,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);
     }
@@ -603,6 +573,44 @@
             long currentTime = System.currentTimeMillis();
             eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED));
         }
+
+        @Override
+        public void onNotificationChannelModified(String pkg, UserHandle user,
+                NotificationChannel channel, int modificationType) {
+            PackageData packageData = getPackage(pkg, user.getIdentifier());
+            String shortcutId = channel.getConversationId();
+            if (packageData == null || shortcutId == null) {
+                return;
+            }
+            ConversationStore conversationStore = packageData.getConversationStore();
+            ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId);
+            if (conversationInfo == null) {
+                return;
+            }
+            ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo);
+            switch (modificationType) {
+                case NOTIFICATION_CHANNEL_OR_GROUP_ADDED:
+                case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED:
+                    builder.setNotificationChannelId(channel.getId());
+                    builder.setImportant(channel.isImportantConversation());
+                    builder.setDemoted(channel.isDemoted());
+                    builder.setNotificationSilenced(
+                            channel.getImportance() <= NotificationManager.IMPORTANCE_LOW);
+                    builder.setBubbled(channel.canBubble());
+                    break;
+                case NOTIFICATION_CHANNEL_OR_GROUP_DELETED:
+                    // If the notification channel is deleted, revert all the notification settings
+                    // to the default value.
+                    builder.setNotificationChannelId(null);
+                    builder.setImportant(false);
+                    builder.setDemoted(false);
+                    builder.setNotificationSilenced(false);
+                    builder.setBubbled(false);
+                    break;
+            }
+            conversationStore.addOrUpdate(builder.build());
+            // TODO: Cache the shortcut when a conversation's notification setting is changed.
+        }
     }
 
     /**
@@ -611,19 +619,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 +688,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/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 8d2a152..6083ce34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -174,12 +174,12 @@
         final int app1ConnectiongGroup = 10;
         final int app1UidUser2 = 1010123;
         final int app1PidUser2 = 12347;
-        final int app1Pss1 = 34567;
-        final int app1Rss1 = 45678;
-        final int app1Pss2 = 34568;
-        final int app1Rss2 = 45679;
-        final int app1Pss3 = 34569;
-        final int app1Rss3 = 45680;
+        final long app1Pss1 = 34567;
+        final long app1Rss1 = 45678;
+        final long app1Pss2 = 34568;
+        final long app1Rss2 = 45679;
+        final long app1Pss3 = 34569;
+        final long app1Rss3 = 45680;
         final String app1ProcessName = "com.android.test.stub1:process";
         final String app1PackageName = "com.android.test.stub1";
 
@@ -344,8 +344,8 @@
         // Case 4: Create a process from another package with kill from lmkd
         final int app2UidUser2 = 1010234;
         final int app2PidUser2 = 12348;
-        final int app2Pss1 = 54321;
-        final int app2Rss1 = 65432;
+        final long app2Pss1 = 54321;
+        final long app2Rss1 = 65432;
         final String app2ProcessName = "com.android.test.stub2:process";
         final String app2PackageName = "com.android.test.stub2";
 
@@ -402,8 +402,8 @@
         final int app3UidUser2 = 1010345;
         final int app3PidUser2 = 12349;
         final int app3ConnectiongGroup = 4;
-        final int app3Pss1 = 54320;
-        final int app3Rss1 = 65430;
+        final long app3Pss1 = 54320;
+        final long app3Rss1 = 65430;
         final String app3ProcessName = "com.android.test.stub3:process";
         final String app3PackageName = "com.android.test.stub3";
         final String app3Description = "native crash";
@@ -529,8 +529,8 @@
         final int app3Uid = 10345;
         final int app3IsolatedUid = 99001; // it's an isolated process
         final int app3Pid = 12350;
-        final int app3Pss2 = 23232;
-        final int app3Rss2 = 32323;
+        final long app3Pss2 = 23232;
+        final long app3Rss2 = 32323;
         final String app3Description2 = "force close";
 
         sleep(1);
@@ -618,8 +618,8 @@
 
         sleep(1);
         final int app1IsolatedUidUser2 = 1099002; // isolated uid
-        final int app1Pss4 = 34343;
-        final int app1Rss4 = 43434;
+        final long app1Pss4 = 34343;
+        final long app1Rss4 = 43434;
         final long now8 = System.currentTimeMillis();
         sigNum = OsConstants.SIGKILL;
         doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum)))
@@ -673,8 +673,8 @@
         sleep(1);
         final int app1Pid2User2 = 56565;
         final int app1IsolatedUid2User2 = 1099003; // isolated uid
-        final int app1Pss5 = 34344;
-        final int app1Rss5 = 43435;
+        final long app1Pss5 = 34344;
+        final long app1Rss5 = 43435;
         final long now9 = System.currentTimeMillis();
         sigNum = OsConstants.SIGKILL;
         doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum)))
@@ -831,7 +831,7 @@
     }
 
     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
-            int connectionGroup, int procState, int pss, int rss,
+            int connectionGroup, int procState, long pss, long rss,
             String processName, String packageName) {
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
@@ -847,8 +847,8 @@
         app.connectionGroup = connectionGroup;
         app.setProcState = procState;
         app.lastMemInfo = spy(new Debug.MemoryInfo());
-        doReturn(pss).when(app.lastMemInfo).getTotalPss();
-        doReturn(rss).when(app.lastMemInfo).getTotalRss();
+        doReturn((int) pss).when(app.lastMemInfo).getTotalPss();
+        doReturn((int) rss).when(app.lastMemInfo).getTotalRss();
         return app;
     }
 
@@ -856,7 +856,7 @@
             Long timestamp, Integer pid, Integer uid, Integer packageUid,
             Integer definingUid, String processName, Integer connectionGroup,
             Integer reason, Integer subReason, Integer status,
-            Integer pss, Integer rss, Integer importance, String description) {
+            Long pss, Long rss, Integer importance, String description) {
         assertNotNull(info);
 
         if (timestamp != null) {
@@ -892,10 +892,10 @@
             assertEquals(status.intValue(), info.getStatus());
         }
         if (pss != null) {
-            assertEquals(pss.intValue(), info.getPss());
+            assertEquals(pss.longValue(), info.getPss());
         }
         if (rss != null) {
-            assertEquals(rss.intValue(), info.getRss());
+            assertEquals(rss.longValue(), info.getRss());
         }
         if (importance != null) {
             assertEquals(importance.intValue(), info.getImportance());
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..3778e17
--- /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 = BlobHandle.createWithSha256("digest1".getBytes(),
+                "label1", System.currentTimeMillis() - 1000, "tag1");
+        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 = BlobHandle.createWithSha256("digest2".getBytes(),
+                "label2", System.currentTimeMillis() + 20000, "tag2");
+        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 = BlobHandle.createWithSha256("digest3".getBytes(),
+                "label3", System.currentTimeMillis() + 30000, "tag3");
+        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..bf2b9be 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",
@@ -75,7 +74,6 @@
         "libbacktrace",
         "libbase",
         "libbinder",
-        "libbinderthreadstate",
         "libc++",
         "libcutils",
         "liblog",
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/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index ce5d6d9..4a686ee 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -28,10 +29,12 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.server.LocalServices;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,6 +51,8 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
     CompatChange.ChangeListener mListener1, mListener2;
     PlatformCompat mPlatformCompat;
     CompatConfig mCompatConfig;
@@ -60,11 +65,15 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
                 new PackageManager.NameNotFoundException());
+        when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt()))
+            .thenReturn(-1);
         mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
         mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
         // Assume userdebug/eng non-final build
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
     }
 
     @Test
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 e0c6d6a..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;
@@ -5446,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 {
@@ -5487,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,
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..d40130a 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -281,9 +281,9 @@
         AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
         allowedInstallers = allowedInstallersCaptor.getValue();
         assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
-        assertEquals(APP_CERT, appInstallMetadata.getAppCertificate());
+        assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
         assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
-        assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate());
+        assertThat(appInstallMetadata.getInstallerCertificates()).containsExactly(INSTALLER_CERT);
         assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
         assertFalse(appInstallMetadata.isPreInstalled());
         // These are hardcoded in the test apk android manifest
@@ -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/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 86daf69..41be54a 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -141,10 +141,10 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName(packageName)
-                        .setAppCertificate(packageCert)
+                        .setAppCertificates(Collections.singletonList(packageCert))
                         .setVersionCode(version)
                         .setInstallerName("abc")
-                        .setInstallerCertificate("abc")
+                        .setInstallerCertificates(Collections.singletonList("abc"))
                         .setIsPreInstalled(true)
                         .build();
         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
@@ -182,10 +182,10 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName(installedPackageName)
-                        .setAppCertificate(installedAppCertificate)
+                        .setAppCertificates(Collections.singletonList(installedAppCertificate))
                         .setVersionCode(250)
                         .setInstallerName("abc")
-                        .setInstallerCertificate("abc")
+                        .setInstallerCertificates(Collections.singletonList("abc"))
                         .setIsPreInstalled(true)
                         .build();
         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index 26b2096..b0b9596 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -70,17 +70,17 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
-                        .setInstallerCertificate(INSTALLER_2_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
+                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
 
         assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
@@ -99,7 +99,7 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
@@ -107,7 +107,7 @@
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -115,7 +115,7 @@
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
+                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -123,7 +123,7 @@
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
+                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -138,7 +138,7 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
@@ -146,7 +146,7 @@
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
-                        .setInstallerCertificate(INSTALLER_2_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
@@ -154,7 +154,7 @@
         AppInstallMetadata appInstallMetadata3 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(INSTALLER_2_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -162,7 +162,7 @@
         AppInstallMetadata appInstallMetadata4 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_2)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -178,7 +178,7 @@
         AppInstallMetadata appInstallMetadata1 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
@@ -186,7 +186,7 @@
         AppInstallMetadata appInstallMetadata2 =
                 getAppInstallMetadataBuilder()
                         .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificate(INSTALLER_1_CERT)
+                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
                         .build();
         assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
                 .isEqualTo(IntegrityCheckResult.Effect.DENY);
@@ -196,8 +196,8 @@
     private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
-                .setAppCertificate("abc")
-                .setInstallerCertificate("abc")
+                .setAppCertificates(Collections.singletonList("abc"))
+                .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
                 .setIsPreInstalled(true);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7b53e5e..b271a77 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -48,7 +48,7 @@
     private static final AppInstallMetadata APP_INSTALL_METADATA =
             new AppInstallMetadata.Builder()
                     .setPackageName(PACKAGE_NAME_1)
-                    .setAppCertificate(APP_CERTIFICATE)
+                    .setAppCertificates(Collections.singletonList(APP_CERTIFICATE))
                     .setVersionCode(2)
                     .build();
 
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 742952e..0784b7a 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -35,6 +35,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(JUnit4.class)
@@ -49,7 +51,7 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName("ddd")
-                        .setAppCertificate("777")
+                        .setAppCertificates(Collections.singletonList("777"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -63,6 +65,29 @@
     }
 
     @Test
+    public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ddd")
+                        .setAppCertificates(Arrays.asList("777", "999"))
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(200, 300),
+                        new RuleIndexRange(700, 800),
+                        new RuleIndexRange(800, 900),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
     public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
         InputStream inputStream = obtainDefaultIndexingMapForTest();
 
@@ -71,7 +96,7 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName("bbb")
-                        .setAppCertificate("999")
+                        .setAppCertificates(Collections.singletonList("999"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -93,7 +118,7 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName("ccc")
-                        .setAppCertificate("444")
+                        .setAppCertificates(Collections.singletonList("444"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -125,7 +150,7 @@
         AppInstallMetadata appInstallMetadata =
                 new AppInstallMetadata.Builder()
                         .setPackageName("ccc")
-                        .setAppCertificate("444")
+                        .setAppCertificates(Collections.singletonList("444"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
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/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 05a9a80..c0e7927 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -47,7 +47,7 @@
                 .setContactPhoneNumber(PHONE_NUMBER)
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
-                .setVip(true)
+                .setImportant(true)
                 .setNotificationSilenced(true)
                 .setBubbled(true)
                 .setDemoted(true)
@@ -62,7 +62,7 @@
         assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
         assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
         assertTrue(conversationInfo.isShortcutLongLived());
-        assertTrue(conversationInfo.isVip());
+        assertTrue(conversationInfo.isImportant());
         assertTrue(conversationInfo.isNotificationSilenced());
         assertTrue(conversationInfo.isBubbled());
         assertTrue(conversationInfo.isDemoted());
@@ -83,7 +83,7 @@
         assertNull(conversationInfo.getContactPhoneNumber());
         assertNull(conversationInfo.getNotificationChannelId());
         assertFalse(conversationInfo.isShortcutLongLived());
-        assertFalse(conversationInfo.isVip());
+        assertFalse(conversationInfo.isImportant());
         assertFalse(conversationInfo.isNotificationSilenced());
         assertFalse(conversationInfo.isBubbled());
         assertFalse(conversationInfo.isDemoted());
@@ -101,7 +101,7 @@
                 .setContactPhoneNumber(PHONE_NUMBER)
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
-                .setVip(true)
+                .setImportant(true)
                 .setNotificationSilenced(true)
                 .setBubbled(true)
                 .setPersonImportant(true)
@@ -110,7 +110,7 @@
                 .build();
 
         ConversationInfo destination = new ConversationInfo.Builder(source)
-                .setVip(false)
+                .setImportant(false)
                 .setContactStarred(false)
                 .build();
 
@@ -120,7 +120,7 @@
         assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
         assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
         assertTrue(destination.isShortcutLongLived());
-        assertFalse(destination.isVip());
+        assertFalse(destination.isImportant());
         assertTrue(destination.isNotificationSilenced());
         assertTrue(destination.isBubbled());
         assertTrue(destination.isPersonImportant());
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..331ad59 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,19 +138,36 @@
         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)
+                .setImportant(true)
                 .setBubbled(true)
                 .build();
     }
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..498d888 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,17 @@
 
 package com.android.server.people.data;
 
-import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 
 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.assertNull;
 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;
@@ -36,11 +38,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Person;
 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;
@@ -92,6 +95,7 @@
     private static final String TEST_SHORTCUT_ID = "sc";
     private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
     private static final String PHONE_NUMBER = "+1234567890";
+    private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
     private static final long MILLIS_PER_MINUTE = 1000L * 60L;
 
     @Mock private Context mContext;
@@ -107,6 +111,7 @@
     @Mock private StatusBarNotification mStatusBarNotification;
     @Mock private Notification mNotification;
 
+    private NotificationChannel mNotificationChannel;
     private DataManager mDataManager;
     private int mCallingUserId;
     private TestInjector mInjector;
@@ -156,6 +161,10 @@
         when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
         when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
 
+        mNotificationChannel = new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT);
+        mNotificationChannel.setConversationId("test", TEST_SHORTCUT_ID);
+
         mCallingUserId = USER_ID_PRIMARY;
 
         mInjector = new TestInjector();
@@ -232,7 +241,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 +272,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());
@@ -287,7 +297,7 @@
     }
 
     @Test
-    public void testNotificationListener() {
+    public void testNotificationOpened() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -310,37 +320,86 @@
     }
 
     @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);
-
+    public void testNotificationChannelCreated() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         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);
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
 
-        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());
+        ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID);
+        assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
+        assertFalse(conversationInfo.isImportant());
+        assertFalse(conversationInfo.isNotificationSilenced());
+        assertFalse(conversationInfo.isDemoted());
+    }
+
+    @Test
+    public void testNotificationChannelModified() {
+        mNotificationChannel.setImportantConversation(true);
+
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+        ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID);
+        assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
+        assertTrue(conversationInfo.isImportant());
+        assertFalse(conversationInfo.isNotificationSilenced());
+        assertFalse(conversationInfo.isDemoted());
+    }
+
+    @Test
+    public void testNotificationChannelDeleted() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+        ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID);
+        assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
+
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+        conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID);
+        assertNull(conversationInfo.getNotificationChannelId());
+        assertFalse(conversationInfo.isImportant());
+        assertFalse(conversationInfo.isNotificationSilenced());
+        assertFalse(conversationInfo.isDemoted());
     }
 
     @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 +427,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/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 3e3f40d..e28d13a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,7 +35,6 @@
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
 import android.content.pm.parsing.PackageImpl;
 import android.content.pm.parsing.ParsingPackage;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
 import android.util.ArrayMap;
@@ -87,6 +86,17 @@
         return pkg;
     }
 
+    private static ParsingPackage pkgQueriesProvider(String packageName,
+            String... queriesAuthorities) {
+        ParsingPackage pkg = pkg(packageName);
+        if (queriesAuthorities != null) {
+            for (String authority : queriesAuthorities) {
+                pkg.addQueriesProvider(authority);
+            }
+        }
+        return pkg;
+    }
+
     private static ParsingPackage pkg(String packageName, String... queriesPackages) {
         ParsingPackage pkg = pkg(packageName);
         if (queriesPackages != null) {
@@ -172,8 +182,7 @@
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
         PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package",
-                        new Intent().setData(Uri.parse("content://com.some.authority"))),
+                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
                 DUMMY_CALLING_UID);
 
         assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -188,8 +197,7 @@
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
         PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package",
-                        new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+                pkgQueriesProvider("com.some.other.package", "com.some.other.authority"),
                 DUMMY_CALLING_UID);
 
         assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -205,8 +213,7 @@
                 pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
                 DUMMY_TARGET_UID);
         PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package",
-                        new Intent().setData(Uri.parse("content://com.some.authority"))),
+                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
                 DUMMY_CALLING_UID);
 
         assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -266,7 +273,7 @@
         appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
-                        pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
+                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_UID);
 
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..77f842a 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;
@@ -555,6 +556,11 @@
         void injectRestoreCallingIdentity(long token) {
             mInjectedCallingUid = (int) token;
         }
+
+        @Override
+        boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
+            return true;
+        }
     }
 
     protected class LauncherAppsTestable extends LauncherApps {
@@ -1600,6 +1606,38 @@
     }
 
     /**
+     * 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 a long lived shortcut with an ID.
+     */
+    protected ShortcutInfo makeLongLivedShortcut(String id) {
+        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))
+                .setLongLived(true);
+        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 +1656,13 @@
     }
 
     /**
+     * Make a LocusId.
+     */
+    protected LocusId makeLocusId(String id) {
+        return new LocusId(id);
+    }
+
+    /**
      * Make an component name, with the client context.
      */
     @NonNull
@@ -1955,16 +2000,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..f036708 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1240,7 +1240,7 @@
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+                    makeLongLivedShortcut("s1"), makeLongLivedShortcut("s2"), makeShortcut("s3"))));
         });
 
         // Pin 2 and 3
@@ -1250,9 +1250,12 @@
         });
 
         // Cache 1 and 2
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
+                    HANDLE_USER_0);
+        });
+
         setCaller(CALLING_PACKAGE_1);
-        getCallerShortcut("s1").setCached();
-        getCallerShortcut("s2").setCached();
 
         // Get manifest shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_MANIFEST),
@@ -1315,8 +1318,9 @@
 
     public void testCachedShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"),
-                    makeShortcut("s3"), makeShortcut("s4"))));
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
+                    makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+                    makeLongLivedShortcut("s4"))));
         });
 
         // Pin s2
@@ -1325,11 +1329,13 @@
                     HANDLE_USER_0);
         });
 
-        // Cache 2, 3 and 4
+        // Cache some, but non long lived shortcuts will be ignored.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s4"),
+                    HANDLE_USER_0);
+        });
+
         setCaller(CALLING_PACKAGE_1);
-        getCallerShortcut("s2").setCached();
-        getCallerShortcut("s3").setCached();
-        getCallerShortcut("s4").setCached();
 
         // Get dynamic shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
@@ -1339,27 +1345,37 @@
                 "s2");
         // Get cached shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s3", "s4");
+                "s2", "s4");
 
         // Remove a dynamic cached shortcut
-        mManager.removeDynamicShortcuts(list("s3"));
+        mManager.removeDynamicShortcuts(list("s4"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2", "s4");
+                "s1", "s2", "s3");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s3", "s4");
+                "s2", "s4");
 
-        // Remove dynamic cached long lived shortcuts
-        mManager.removeLongLivedShortcuts(list("s3", "s4"));
-        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2");
+        // uncache a non-dynamic shortcut. Should be removed.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
+                    HANDLE_USER_0);
+        });
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
                 "s2");
 
+        // Cache another shortcut
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"),
+                    HANDLE_USER_0);
+        });
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+                "s2", "s3");
+
         // Remove a dynamic cached pinned long lived shortcut
         mManager.removeLongLivedShortcuts(list("s2"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1");
-        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED));
+                "s1", "s3");
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+                "s3");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
                 "s2");
     }
@@ -1371,8 +1387,8 @@
         // Set up shortcuts.
 
         setCaller(CALLING_PACKAGE_1);
-        final ShortcutInfo s1_1 = makeShortcut("s1");
-        final ShortcutInfo s1_2 = makeShortcut("s2");
+        final ShortcutInfo s1_1 = makeLongLivedShortcut("s1");
+        final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1"));
 
         assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
 
@@ -1394,7 +1410,9 @@
         getCallerShortcut("s4").setTimestamp(500);
 
         setCaller(CALLING_PACKAGE_3);
-        final ShortcutInfo s3_2 = makeShortcut("s3");
+        final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
+        s3_2.setLongLived();
+
         assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
 
         getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
@@ -1446,7 +1464,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 +1472,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,32 +1547,26 @@
         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());
                 });
 
         // TODO More tests: pinned but dynamic.
 
-        // Cache some shortcuts
-        setCaller(CALLING_PACKAGE_1);
-        getCallerShortcut("s1").setCached();
-
-        setCaller(CALLING_PACKAGE_2);
-        getCallerShortcut("s4").setCached();
-
-        setCaller(CALLING_PACKAGE_3);
-        getCallerShortcut("s3").setCached();
-
         setCaller(LAUNCHER_1);
 
+        // Cache some shortcuts. Only long lived shortcuts can get cached.
+        mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser());
+        mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser());
+
         // Cached ones only
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mLauncherApps.getShortcuts(buildQuery(
-                        /* time =*/ 0, CALLING_PACKAGE_2,
+                        /* time =*/ 0, CALLING_PACKAGE_3,
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_MATCH_CACHED),
                         getCallingUser())),
-                "s4");
+                "s3");
 
         // All packages.
         assertShortcutIds(assertAllNotKeyFieldsOnly(
@@ -1532,12 +1575,12 @@
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_MATCH_CACHED),
                         getCallingUser())),
-                "s1", "s4", "s3");
+                "s1", "s3");
 
         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());
                 });
 
@@ -1550,7 +1593,7 @@
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_MATCH_CACHED),
                         getCallingUser())),
-                "s1", "s4", "s3");
+                "s1", "s3");
     }
 
     public void testGetShortcuts_shortcutKinds() throws Exception {
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 804c1b9..1a6c6b4 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -224,21 +224,43 @@
     }
 
     @Test
-    public void snapshotThenDelete() {
+    public void snapshotThenDeleteNoApex() {
         Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER);
         PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
-        PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+        PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, false);
         rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
 
-        int[] userIds = {12, 18};
+        int[] userIds = {111, 222};
         rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
 
         verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
 
         rollback.delete(mMockDataHelper);
 
-        verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12));
-        verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18));
+        verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111));
+        verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222));
+        verify(mMockDataHelper, never()).destroyApexDeSnapshots(anyInt());
+
+        assertThat(rollback.isDeleted()).isTrue();
+    }
+
+    @Test
+    public void snapshotThenDeleteWithApex() {
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER);
+        PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+        int[] userIds = {111, 222};
+        rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+        verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
+
+        rollback.delete(mMockDataHelper);
+
+        verify(mMockDataHelper, never())
+                .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt());
+        verify(mMockDataHelper).destroyApexDeSnapshots(123);
 
         assertThat(rollback.isDeleted()).isTrue();
     }
diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
index ba493d4..d1c9643 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
@@ -20,7 +20,11 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -36,6 +40,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
+
 @RunWith(JUnit4.class)
 public class WatchdogRollbackLoggerTest {
 
@@ -46,6 +52,11 @@
     private PackageInfo mPackageInfo;
 
     private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
+    private static final String LOGGING_PARENT_VALUE = "logging.parent";
+    private static final int PACKAGE_INFO_FLAGS = PackageManager.MATCH_APEX
+            | PackageManager.GET_META_DATA;
+    private static final List<String> sFailingPackages =
+            List.of("package1", "package2", "package3");
 
     @Before
     public void setUp() {
@@ -64,10 +75,12 @@
      */
     @Test
     public void testLogPackageHasNoMetadata() throws Exception {
-        when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo);
+        when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
         VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
                 sTestPackageV1);
         assertThat(logPackage).isNull();
+        verify(mMockPm, times(1)).getPackageInfo(
+                sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS);
     }
 
     /**
@@ -76,12 +89,16 @@
      */
     @Test
     public void testLogPackageParentKeyIsNull() throws Exception {
-        when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo);
+        when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
         Bundle bundle = new Bundle();
         bundle.putString(LOGGING_PARENT_KEY, null);
+        mApplicationInfo.metaData = bundle;
+        mPackageInfo.applicationInfo = mApplicationInfo;
         VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
                 sTestPackageV1);
         assertThat(logPackage).isNull();
+        verify(mMockPm, times(1)).getPackageInfo(
+                sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS);
     }
 
     /**
@@ -90,15 +107,18 @@
     @Test
     public void testLogPackageHasParentKey() throws Exception {
         Bundle bundle = new Bundle();
-        bundle.putString(LOGGING_PARENT_KEY, "logging.parent");
+        bundle.putString(LOGGING_PARENT_KEY, LOGGING_PARENT_VALUE);
         mApplicationInfo.metaData = bundle;
+        mPackageInfo.applicationInfo = mApplicationInfo;
         mPackageInfo.setLongVersionCode(12345L);
-        when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo);
         when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
         VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
                 sTestPackageV1);
-        VersionedPackage expectedLogPackage = new VersionedPackage("logging.parent", 12345);
+        VersionedPackage expectedLogPackage = new VersionedPackage(LOGGING_PARENT_VALUE, 12345);
         assertThat(logPackage).isEqualTo(expectedLogPackage);
+        verify(mMockPm, times(1)).getPackageInfo(
+                sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS);
+
     }
 
     /**
@@ -107,12 +127,64 @@
     @Test
     public void testLogPackageNameNotFound() throws Exception {
         Bundle bundle = new Bundle();
-        bundle.putString("android.content.pm.LOGGING_PARENT", "logging.parent");
+        bundle.putString(LOGGING_PARENT_KEY, LOGGING_PARENT_VALUE);
         mApplicationInfo.metaData = bundle;
-        when(mMockPm.getPackageInfo(anyString(), anyInt())).thenThrow(
+        mPackageInfo.applicationInfo = mApplicationInfo;
+        when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
+        when(mMockPm.getPackageInfo(same(LOGGING_PARENT_VALUE), anyInt())).thenThrow(
                 new PackageManager.NameNotFoundException());
         VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
                 sTestPackageV1);
         assertThat(logPackage).isNull();
+        verify(mMockPm, times(1)).getPackageInfo(
+                sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS);
+    }
+
+    /**
+     * Ensures that we make the correct Package Manager calls in the case that the failing packages
+     * are correctly configured with parent packages.
+     */
+    @Test
+    public void testApexdLoggingCallsWithParents() throws Exception {
+        for (String failingPackage: sFailingPackages) {
+            PackageInfo packageInfo = new PackageInfo();
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            Bundle bundle = new Bundle();
+            bundle.putString(LOGGING_PARENT_KEY, getParent(failingPackage));
+            applicationInfo.metaData = bundle;
+            packageInfo.applicationInfo = applicationInfo;
+            when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo);
+        }
+
+        when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo);
+        WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process");
+        for (String failingPackage: sFailingPackages) {
+            verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS);
+            verify(mMockPm, times(1)).getPackageInfo(getParent(failingPackage), 0);
+        }
+    }
+
+    /**
+     * Ensures that we don't make any calls to parent packages in the case that packages are not
+     * correctly configured with parent packages.
+     */
+    @Test
+    public void testApexdLoggingCallsWithNoParents() throws Exception {
+        for (String failingPackage: sFailingPackages) {
+            PackageInfo packageInfo = new PackageInfo();
+            packageInfo.applicationInfo = new ApplicationInfo();
+            when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo);
+        }
+        when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo);
+
+        WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process");
+        verify(mMockPm, times(sFailingPackages.size())).getPackageInfo(anyString(), anyInt());
+        for (String failingPackage: sFailingPackages) {
+            verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS);
+        }
+    }
+
+    private String getParent(String packageName) {
+        return packageName + "-parent";
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index fde0ddf..4d0481be 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -144,6 +144,40 @@
         assertEquals("Incorrect blacklist", expectedBlack, actualBlack);
     }
 
+    @Test
+    public void testComponentOverride() throws Exception {
+        final String contents =
+                "<permissions>"
+                + "    <component-override package=\"com.android.package1\">\n"
+                + "        <component class=\"com.android.package1.Full\" enabled=\"true\"/>"
+                + "        <component class=\".Relative\" enabled=\"false\" />\n"
+                + "    </component-override>"
+                + "    <component-override package=\"com.android.package2\" >\n"
+                + "        <component class=\"com.android.package3.Relative2\" enabled=\"yes\" />\n"
+                + "    </component-override>\n"
+                + "</permissions>";
+
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "component-override.xml", contents);
+
+        mSysConfig.readPermissions(folder, /* No permission needed anyway */ 0);
+
+        final ArrayMap<String, Boolean>  packageOneExpected = new ArrayMap<>();
+        packageOneExpected.put("com.android.package1.Full", true);
+        packageOneExpected.put("com.android.package1.Relative", false);
+
+        final ArrayMap<String, Boolean> packageTwoExpected = new ArrayMap<>();
+        packageTwoExpected.put("com.android.package3.Relative2", true);
+
+        final Map<String, Boolean> packageOne = mSysConfig.getComponentsEnabledStates(
+                "com.android.package1");
+        assertEquals(packageOneExpected, packageOne);
+
+        final Map<String, Boolean> packageTwo = mSysConfig.getComponentsEnabledStates(
+                "com.android.package2");
+        assertEquals(packageTwoExpected, packageTwo);
+    }
+
     /**
      * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
      * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
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/Android.bp b/services/tests/uiservicestests/Android.bp
index f608bab..3f0cda3 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,7 +47,6 @@
         "libbacktrace",
         "libbase",
         "libbinder",
-        "libbinderthreadstate",
         "libc++",
         "libcutils",
         "liblog",
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/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 5b5ad87..cbb760a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -44,6 +44,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.internal.matchers.Not;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -239,6 +240,52 @@
         verify(af2, never()).openRead();
     }
 
+    @Test
+    public void testRemoveNotificationRunnable() throws Exception {
+        NotificationHistory nh = mock(NotificationHistory.class);
+        NotificationHistoryDatabase.RemoveNotificationRunnable rnr =
+                mDataBase.new RemoveNotificationRunnable("pkg", 123);
+        rnr.setNotificationHistory(nh);
+
+        AtomicFile af = mock(AtomicFile.class);
+        when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
+        mDataBase.mHistoryFiles.addLast(af);
+
+        when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true);
+
+        mDataBase.mBuffer = mock(NotificationHistory.class);
+
+        rnr.run();
+
+        verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123);
+        verify(af).openRead();
+        verify(nh).removeNotificationFromWrite("pkg", 123);
+        verify(af).startWrite();
+    }
+
+    @Test
+    public void testRemoveNotificationRunnable_noChanges() throws Exception {
+        NotificationHistory nh = mock(NotificationHistory.class);
+        NotificationHistoryDatabase.RemoveNotificationRunnable rnr =
+                mDataBase.new RemoveNotificationRunnable("pkg", 123);
+        rnr.setNotificationHistory(nh);
+
+        AtomicFile af = mock(AtomicFile.class);
+        when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
+        mDataBase.mHistoryFiles.addLast(af);
+
+        when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false);
+
+        mDataBase.mBuffer = mock(NotificationHistory.class);
+
+        rnr.run();
+
+        verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123);
+        verify(af).openRead();
+        verify(nh).removeNotificationFromWrite("pkg", 123);
+        verify(af, never()).startWrite();
+    }
+
     private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider {
         public Map<File, Long> creationDates = new HashMap<>();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index b5eb1bf..2c548be 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -289,6 +289,20 @@
     }
 
     @Test
+    public void testDeleteNotificationHistoryItem_userUnlocked() {
+        String pkg = "pkg";
+        long time = 235;
+        NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
+
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+        mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
+
+        mHistoryManager.deleteNotificationHistoryItem(pkg, 1, time);
+
+        verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time);
+    }
+
+    @Test
     public void testTriggerWriteToDisk() {
         NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class);
         NotificationHistoryDatabase userHistoryAll = mock(NotificationHistoryDatabase.class);
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 ad5be43..57428dc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -32,7 +32,6 @@
 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;
@@ -42,6 +41,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -50,6 +51,8 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 
@@ -70,6 +73,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;
@@ -127,6 +131,7 @@
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
+import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
@@ -1143,7 +1148,9 @@
         waitForIdle();
         assertEquals(1, mNotificationRecordLogger.getCalls().size());
         NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
-        assertTrue(call.shouldLog());
+        assertTrue(call.shouldLog);
+        assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                call.event);
         assertNotNull(call.r);
         assertNull(call.old);
         assertEquals(0, call.position);
@@ -1152,7 +1159,7 @@
         assertEquals(0, call.r.getSbn().getId());
         assertEquals(tag, call.r.getSbn().getTag());
         assertNotNull(call.r.getSbn().getInstanceId());
-        assertEquals(0, call.r.getSbn().getInstanceId().getId());
+        assertEquals(0, call.getInstanceId());  // Fake instance IDs are assigned in order
     }
 
     @Test
@@ -1170,18 +1177,18 @@
         waitForIdle();
         assertEquals(2, mNotificationRecordLogger.getCalls().size());
 
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog());
+        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
         assertEquals(
-                NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).getUiEvent());
-        assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId());
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                mNotificationRecordLogger.get(0).event);
+        assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
 
-        assertTrue(mNotificationRecordLogger.get(1).shouldLog());
+        assertTrue(mNotificationRecordLogger.get(1).shouldLog);
         assertEquals(
-                NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_UPDATED,
-                mNotificationRecordLogger.get(1).getUiEvent());
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
+                mNotificationRecordLogger.get(1).event);
         // Instance ID doesn't change on update of an active notification
-        assertEquals(0, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId());
+        assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
     }
 
     @Test
@@ -1193,8 +1200,8 @@
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         assertEquals(2, mNotificationRecordLogger.getCalls().size());
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog());
-        assertFalse(mNotificationRecordLogger.get(1).shouldLog());
+        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+        assertFalse(mNotificationRecordLogger.get(1).shouldLog);
     }
 
     @Test
@@ -1209,20 +1216,37 @@
         waitForIdle();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
         waitForIdle();
-        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(3, mNotificationRecordLogger.getCalls().size());
 
-        assertTrue(mNotificationRecordLogger.get(0).shouldLog());
         assertEquals(
-                NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).getUiEvent());
-        assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId());
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                mNotificationRecordLogger.get(0).event);
+        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+        assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
 
-        assertTrue(mNotificationRecordLogger.get(1).shouldLog());
+        assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason);
         assertEquals(
-                NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(1).getUiEvent());
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
+                mNotificationRecordLogger.get(1).event);
+        assertTrue(mNotificationRecordLogger.get(1).shouldLog);
+        assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
+
+        assertEquals(
+                NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+                mNotificationRecordLogger.get(2).event);
+        assertTrue(mNotificationRecordLogger.get(2).shouldLog);
         // New instance ID because notification was canceled before re-post
-        assertEquals(1, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId());
+        assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId());
+    }
+
+    @Test
+    public void testCancelNonexistentNotification() throws Exception {
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testCancelNonexistentNotification", 0, 0);
+        waitForIdle();
+        // The notification record logger doesn't even get called when a nonexistent notification
+        // is cancelled, because that happens very frequently and is not interesting.
+        assertEquals(0, mNotificationRecordLogger.getCalls().size());
     }
 
     @Test
@@ -3364,6 +3388,17 @@
         waitForIdle();
 
         assertEquals(NotificationStats.DISMISSAL_AOD, r.getStats().getDismissalSurface());
+
+        // Using mService.addNotification() does not generate a NotificationRecordLogger log,
+        // so we only get the cancel notification.
+        assertEquals(1, mNotificationRecordLogger.getCalls().size());
+
+        assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason);
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
+                mNotificationRecordLogger.get(0).event);
+        assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+        assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
     }
 
     @Test
@@ -3644,6 +3679,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");
@@ -6245,4 +6307,60 @@
 
         assertEquals(PRIORITY_CATEGORY_CALLS, actual);
     }
+
+    @Test
+    public void testGetConversationsForPackage_hasShortcut() throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        ArrayList<ConversationChannelWrapper> convos = new ArrayList<>();
+        ConversationChannelWrapper convo1 = new ConversationChannelWrapper();
+        NotificationChannel channel1 = new NotificationChannel("a", "a", 1);
+        channel1.setConversationId("parent1", "convo 1");
+        convo1.setNotificationChannel(channel1);
+        convos.add(convo1);
+
+        ConversationChannelWrapper convo2 = new ConversationChannelWrapper();
+        NotificationChannel channel2 = new NotificationChannel("b", "b", 1);
+        channel2.setConversationId("parent1", "convo 2");
+        convo2.setNotificationChannel(channel2);
+        convos.add(convo2);
+        when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
+
+        // only one valid shortcut
+        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+                .setPackage(PKG_P)
+                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                .setShortcutIds(Arrays.asList(channel1.getConversationId()));
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getShortLabel()).thenReturn("Hello");
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si));
+
+        List<ConversationChannelWrapper> conversations =
+                mBinderService.getConversationsForPackage(PKG_P, mUid).getList();
+        assertEquals(si, conversations.get(0).getShortcutInfo());
+        assertEquals(si, conversations.get(1).getShortcutInfo());
+
+    }
+
+    @Test
+    public void testGetConversationsForPackage_doesNotHaveShortcut() throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        ArrayList<ConversationChannelWrapper> convos = new ArrayList<>();
+        ConversationChannelWrapper convo1 = new ConversationChannelWrapper();
+        NotificationChannel channel1 = new NotificationChannel("a", "a", 1);
+        channel1.setConversationId("parent1", "convo 1");
+        convo1.setNotificationChannel(channel1);
+        convos.add(convo1);
+
+        ConversationChannelWrapper convo2 = new ConversationChannelWrapper();
+        NotificationChannel channel2 = new NotificationChannel("b", "b", 1);
+        channel2.setConversationId("parent1", "convo 2");
+        convo2.setNotificationChannel(channel2);
+        convos.add(convo2);
+        when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
+
+        List<ConversationChannelWrapper> conversations =
+                mBinderService.getConversationsForPackage(PKG_P, mUid).getList();
+        assertNull(conversations.get(0).getShortcutInfo());
+        assertNull(conversations.get(1).getShortcutInfo());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 5c1487f..b120dbe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import com.android.internal.logging.UiEventLogger;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -24,18 +26,27 @@
  */
 class NotificationRecordLoggerFake implements NotificationRecordLogger {
     static class CallRecord extends NotificationRecordPair {
-        public int position, buzzBeepBlink;
+        static final int INVALID = -1;
+        public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID;
+        public boolean shouldLog;
+        public UiEventLogger.UiEventEnum event;
         CallRecord(NotificationRecord r, NotificationRecord old, int position,
                 int buzzBeepBlink) {
             super(r, old);
+
             this.position = position;
             this.buzzBeepBlink = buzzBeepBlink;
+            shouldLog = shouldLog(buzzBeepBlink);
+            event = NotificationReportedEvent.fromRecordPair(this);
         }
-        boolean shouldLog() {
-            return shouldLog(buzzBeepBlink);
+        CallRecord(NotificationRecord r, int reason, int dismissalSurface) {
+            super(r, null);
+            this.reason = reason;
+            shouldLog = true;
+            event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface);
         }
     }
-    private List<CallRecord> mCalls = new ArrayList<CallRecord>();
+    private List<CallRecord> mCalls = new ArrayList<>();
 
     List<CallRecord> getCalls() {
         return mCalls;
@@ -50,4 +61,10 @@
             int position, int buzzBeepBlink) {
         mCalls.add(new CallRecord(r, old, position, buzzBeepBlink));
     }
+
+    @Override
+    public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
+        mCalls.add(new CallRecord(r, reason, dismissalSurface));
+    }
+
 }
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 bb84b04..3f690b1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -26,6 +26,8 @@
 
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.fail;
 
@@ -64,6 +66,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+import android.service.notification.ConversationChannelWrapper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
@@ -91,6 +94,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -2939,4 +2943,97 @@
 
         assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
     }
+
+    @Test
+    public void testGetConversations_invalidPkg() {
+        assertThat(mHelper.getConversations("bad", 1)).isEmpty();
+    }
+
+    @Test
+    public void testGetConversations_noConversations() {
+        NotificationChannel channel =
+                new NotificationChannel("not_convo", "not_convo", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty();
+    }
+
+    @Test
+    public void testGetConversations_noDisabledGroups() {
+        NotificationChannelGroup group = new NotificationChannelGroup("a", "a");
+        group.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        NotificationChannel parent = new NotificationChannel("parent", "p", 1);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "convo");
+        channel.setGroup(group.getId());
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty();
+    }
+
+    @Test
+    public void testGetConversations_noDeleted() {
+        NotificationChannel parent = new NotificationChannel("parent", "p", 1);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+        NotificationChannel channel =
+                new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "convo");
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+        mHelper.deleteNotificationChannel(PKG_O, UID_O, channel.getId());
+
+        assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty();
+    }
+
+    @Test
+    public void testGetConversations() {
+        NotificationChannelGroup group = new NotificationChannelGroup("acct", "account_name");
+        mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+
+        NotificationChannel messages =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        messages.setGroup(group.getId());
+        mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+        NotificationChannel calls =
+                new NotificationChannel("calls", "Calls", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("A person", "A lovely person", IMPORTANCE_DEFAULT);
+        channel.setGroup(group.getId());
+        channel.setConversationId(messages.getId(), channel.getName().toString());
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        NotificationChannel channel2 =
+                new NotificationChannel("B person", "B fabulous person", IMPORTANCE_DEFAULT);
+        channel2.setConversationId(calls.getId(), channel.getName().toString());
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+
+        Map<String, NotificationChannel> expected = new HashMap<>();
+        expected.put(channel.getId(), channel);
+        expected.put(channel2.getId(), channel2);
+
+        Map<String, CharSequence> expectedGroup = new HashMap<>();
+        expectedGroup.put(channel.getId(), group.getName());
+        expectedGroup.put(channel2.getId(), null);
+
+        Map<String, CharSequence> expectedParentLabel= new HashMap<>();
+        expectedParentLabel.put(channel.getId(), messages.getName());
+        expectedParentLabel.put(channel2.getId(), calls.getName());
+
+        ArrayList<ConversationChannelWrapper> convos = mHelper.getConversations(PKG_O, UID_O);
+        assertThat(convos).hasSize(2);
+
+        for (ConversationChannelWrapper convo : convos) {
+            assertThat(convo.getNotificationChannel())
+                    .isEqualTo(expected.get(convo.getNotificationChannel().getId()));
+            assertThat(convo.getParentChannelLabel())
+                    .isEqualTo(expectedParentLabel.get(convo.getNotificationChannel().getId()));
+            assertThat(convo.getGroupLabel())
+                    .isEqualTo(expectedGroup.get(convo.getNotificationChannel().getId()));
+        }
+    }
 }
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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ebe4ab9..a0ea729 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -140,6 +141,19 @@
     }
 
     @Test
+    public void testRemoveChildWithOverlayActivity() {
+        final ActivityRecord overlayActivity =
+                new ActivityBuilder(mService).setTask(mTask).build();
+        overlayActivity.setTaskOverlay(true);
+        final ActivityRecord overlayActivity2 =
+                new ActivityBuilder(mService).setTask(mTask).build();
+        overlayActivity2.setTaskOverlay(true);
+
+        mTask.removeChild(overlayActivity2, "test");
+        verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any());
+    }
+
+    @Test
     public void testNoCleanupMovingActivityInSameStack() {
         final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
         mActivity.reparent(newTask, 0, null /*reason*/);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 9dbaa4c..e6291a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -95,8 +95,6 @@
 
     @Before
     public void setUp() throws Exception {
-        updateDisplayFrames();
-
         mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
         // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
         // changing those frames.
@@ -106,6 +104,8 @@
         attrs.width = MATCH_PARENT;
         attrs.height = MATCH_PARENT;
         attrs.format = PixelFormat.TRANSLUCENT;
+
+        updateDisplayFrames();
     }
 
     @After
@@ -126,6 +126,7 @@
 
     private void updateDisplayFrames() {
         mFrames = createDisplayFrames();
+        mDisplayContent.mDisplayFrames = mFrames;
     }
 
     private DisplayFrames createDisplayFrames() {
@@ -180,63 +181,6 @@
     }
 
     @Test
-    public void layoutWindowLw_appDrawsBars() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
-        addWindow(mWindow);
-
-        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
-        assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-    }
-
-    @Test
-    public void layoutWindowLw_forceAppDrawBars() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
-        addWindow(mWindow);
-
-        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
-        assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-    }
-
-    @Test
-    public void layoutWindowLw_onlyDrawBottomBar() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
-        addWindow(mWindow);
-
-        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-    }
-
-    @Test
     public void layoutWindowLw_fitAllSides() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
@@ -273,7 +217,7 @@
     }
 
     @Test
-    public void layoutWindowLw_fitMax() {
+    public void layoutWindowLw_fitInsetsIgnoringVisibility() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         final InsetsState state =
@@ -295,7 +239,7 @@
     }
 
     @Test
-    public void layoutWindowLw_fitNonMax() {
+    public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         final InsetsState state =
@@ -398,6 +342,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -416,6 +361,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
         addWindow(mWindow);
 
@@ -435,6 +381,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         mWindow.mAttrs.setFitInsetsTypes(
                 mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -456,6 +403,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
         mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -477,6 +425,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
         mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -501,6 +450,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -521,6 +471,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -541,6 +492,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         mWindow.mAttrs.setFitInsetsTypes(
                 mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -581,6 +533,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         mWindow.mAttrs.setFitInsetsTypes(
                 mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -603,6 +556,7 @@
 
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
         addWindow(mWindow);
 
@@ -625,6 +579,7 @@
     public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
         addWindow(mWindow);
 
@@ -849,7 +804,6 @@
         final String prefix = "";
         final InsetsState simulatedInsetsState = new InsetsState();
         final DisplayFrames simulatedDisplayFrames = createDisplayFrames();
-        mDisplayContent.mDisplayFrames = mFrames;
         mDisplayPolicy.beginLayoutLw(mFrames, uiMode);
         mDisplayContent.getInsetsStateController().onPostLayout();
         mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode);
@@ -867,6 +821,7 @@
         // Exclude comparing IME insets because currently the simulated layout only focuses on the
         // insets from status bar and navigation bar.
         realInsetsState.removeSource(InsetsState.ITYPE_IME);
+        realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
         realInsetsState.dump(prefix, new PrintWriter(realInsetsDump));
         final StringWriter simulatedInsetsDump = new StringWriter();
         simulatedInsetsState.dump(prefix, new PrintWriter(simulatedInsetsDump));
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/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 078347e..f7aa3cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -26,8 +26,10 @@
 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.never;
 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;
@@ -36,14 +38,17 @@
 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;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
 import android.view.Display;
 import android.view.ITaskOrganizer;
 import android.view.IWindowContainer;
@@ -139,6 +144,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 +212,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 +227,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 +240,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());
     }
 
@@ -308,6 +359,78 @@
         assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
     }
 
+    @Test
+    public void testHierarchyTransaction() {
+        final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
+        ITaskOrganizer listener = new ITaskOrganizer.Stub() {
+            @Override
+            public void taskAppeared(RunningTaskInfo taskInfo) { }
+
+            @Override
+            public void taskVanished(IWindowContainer container) { }
+
+            @Override
+            public void transactionReady(int id, SurfaceControl.Transaction t) { }
+
+            @Override
+            public void onTaskInfoChanged(RunningTaskInfo info) {
+                lastReportedTiles.put(info.token.asBinder(), info);
+            }
+        };
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
+                listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+                mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+                mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+        final ActivityStack stack = createTaskStackOnDisplay(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final ActivityStack stack2 = createTaskStackOnDisplay(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+
+        lastReportedTiles.clear();
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+        wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+                null /* organizer */);
+        assertFalse(lastReportedTiles.isEmpty());
+        assertEquals(ACTIVITY_TYPE_STANDARD,
+                lastReportedTiles.get(info1.token.asBinder()).topActivityType);
+        assertEquals(ACTIVITY_TYPE_HOME,
+                lastReportedTiles.get(info2.token.asBinder()).topActivityType);
+
+        lastReportedTiles.clear();
+        wct = new WindowContainerTransaction();
+        wct.reparent(stack2.mRemoteToken, info1.token, false /* onTop */);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+                null /* organizer */);
+        assertFalse(lastReportedTiles.isEmpty());
+        // Standard should still be on top of tile 1, so no change there
+        assertFalse(lastReportedTiles.containsKey(info1.token.asBinder()));
+        // But tile 2 has no children, so should become undefined
+        assertEquals(ACTIVITY_TYPE_UNDEFINED,
+                lastReportedTiles.get(info2.token.asBinder()).topActivityType);
+
+        // Check the getChildren call
+        List<RunningTaskInfo> children =
+                mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token);
+        assertEquals(2, children.size());
+        children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token);
+        assertEquals(0, children.size());
+
+        lastReportedTiles.clear();
+        wct = new WindowContainerTransaction();
+        wct.reorder(stack2.mRemoteToken, true /* onTop */);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+                null /* organizer */);
+        // Home should now be on top. No change occurs in second tile, so not reported
+        assertEquals(1, lastReportedTiles.size());
+        assertEquals(ACTIVITY_TYPE_HOME,
+                lastReportedTiles.get(info1.token.asBinder()).topActivityType);
+    }
+
     private List<TaskTile> getTaskTiles(DisplayContent dc) {
         ArrayList<TaskTile> out = new ArrayList<>();
         for (int i = dc.getStackCount() - 1; i >= 0; --i) {
@@ -318,4 +441,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/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index 7aaf3fb..52b465f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -20,7 +20,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
+import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
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/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
index 5172b7b..a3894d6 100644
--- a/telephony/common/com/google/android/mms/pdu/CharacterSets.java
+++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
@@ -48,6 +48,51 @@
     public static final int UTF_16      = 0x03F7;
 
     /**
+     * Extend charsets.
+     *
+     * From http://www.iana.org/assignments/character-sets/
+     */
+    public static final int BIG5_HKSCS = 0x0835; //2101
+    public static final int BOCU_1 = 0x03FC; //1020
+    public static final int CESU_8 = 0x03F8; //1016
+    public static final int CP864 = 0x0803; //2051
+    public static final int EUC_JP = 0x12; //18
+    public static final int EUC_KR = 0x26; //38
+    public static final int GB18030 = 0x72; //114
+    public static final int GBK = 0x71; //113
+    public static final int HZ_GB_2312 = 0x0825; //2085
+    public static final int GB_2312 = 0x07E9; //2025
+    public static final int ISO_2022_CN = 0x68; //104
+    public static final int ISO_2022_CN_EXT = 0x69; //105
+    public static final int ISO_2022_JP = 0x27; //39
+    public static final int ISO_2022_KR = 0x25; //37
+    public static final int ISO_8859_10 = 0x0D; //13
+    public static final int ISO_8859_13 = 0x6D; //109
+    public static final int ISO_8859_14 = 0x6E; //110
+    public static final int ISO_8859_15 = 0x6F; //111
+    public static final int ISO_8859_16 = 0x70; //112
+    public static final int KOI8_R = 0x0824; //2084
+    public static final int KOI8_U = 0x0828; //2088
+    public static final int MACINTOSH = 0x07EB; //2027
+    public static final int SCSU = 0x03F3; //1011
+    public static final int TIS_620 = 0x08D3; //2259
+    public static final int UTF_16BE = 0x03F5; //1013
+    public static final int UTF_16LE = 0x03F6; //1014
+    public static final int UTF_32 = 0x03F9; //1017
+    public static final int UTF_32BE = 0x03FA; //1018
+    public static final int UTF_32LE = 0x03FB; //1019
+    public static final int UTF_7 = 0x03F4; //1012
+    public static final int WINDOWS_1250 = 0x08CA; //2250
+    public static final int WINDOWS_1251 = 0x08CB; //2251
+    public static final int WINDOWS_1252 = 0x08CC; //2252
+    public static final int WINDOWS_1253 = 0x08CD; //2253
+    public static final int WINDOWS_1254 = 0x08CE; //2254
+    public static final int WINDOWS_1255 = 0x08CF; //2255
+    public static final int WINDOWS_1256 = 0x08D0; //2256
+    public static final int WINDOWS_1257 = 0x08D1; //2257
+    public static final int WINDOWS_1258 = 0x08D2; //2258
+
+    /**
      * If the encoding of given data is unsupported, use UTF_8 to decode it.
      */
     public static final int DEFAULT_CHARSET = UTF_8;
@@ -72,6 +117,45 @@
         BIG5,
         UCS2,
         UTF_16,
+        BIG5_HKSCS,
+        BOCU_1,
+        CESU_8,
+        CP864,
+        EUC_JP,
+        EUC_KR,
+        GB18030,
+        GBK,
+        HZ_GB_2312,
+        GB_2312,
+        ISO_2022_CN,
+        ISO_2022_CN_EXT,
+        ISO_2022_JP,
+        ISO_2022_KR,
+        ISO_8859_10,
+        ISO_8859_13,
+        ISO_8859_14,
+        ISO_8859_15,
+        ISO_8859_16,
+        KOI8_R,
+        KOI8_U,
+        MACINTOSH,
+        SCSU,
+        TIS_620,
+        UTF_16BE,
+        UTF_16LE,
+        UTF_32,
+        UTF_32BE,
+        UTF_32LE,
+        UTF_7,
+        WINDOWS_1250,
+        WINDOWS_1251,
+        WINDOWS_1252,
+        WINDOWS_1253,
+        WINDOWS_1254,
+        WINDOWS_1255,
+        WINDOWS_1256,
+        WINDOWS_1257,
+        WINDOWS_1258,
     };
 
     /**
@@ -94,6 +178,51 @@
     public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
     public static final String MIMENAME_UTF_16      = "utf-16";
 
+    /**
+     * Extend charsets.
+     *
+     * From http://www.iana.org/assignments/character-sets/
+     */
+    public static final String MIMENAME_BIG5_HKSCS = "Big5-HKSCS";
+    public static final String MIMENAME_BOCU_1 = "BOCU-1";
+    public static final String MIMENAME_CESU_8 = "CESU-8";
+    public static final String MIMENAME_CP864 = "cp864";
+    public static final String MIMENAME_EUC_JP = "EUC-JP";
+    public static final String MIMENAME_EUC_KR = "EUC-KR";
+    public static final String MIMENAME_GB18030 = "GB18030";
+    public static final String MIMENAME_GBK = "GBK";
+    public static final String MIMENAME_HZ_GB_2312 = "HZ-GB-2312";
+    public static final String MIMENAME_GB_2312 = "GB2312";
+    public static final String MIMENAME_ISO_2022_CN = "ISO-2022-CN";
+    public static final String MIMENAME_ISO_2022_CN_EXT = "ISO-2022-CN-EXT";
+    public static final String MIMENAME_ISO_2022_JP = "ISO-2022-JP";
+    public static final String MIMENAME_ISO_2022_KR = "ISO-2022-KR";
+    public static final String MIMENAME_ISO_8859_10 = "ISO-8859-10";
+    public static final String MIMENAME_ISO_8859_13 = "ISO-8859-13";
+    public static final String MIMENAME_ISO_8859_14 = "ISO-8859-14";
+    public static final String MIMENAME_ISO_8859_15 = "ISO-8859-15";
+    public static final String MIMENAME_ISO_8859_16 = "ISO-8859-16";
+    public static final String MIMENAME_KOI8_R = "KOI8-R";
+    public static final String MIMENAME_KOI8_U = "KOI8-U";
+    public static final String MIMENAME_MACINTOSH = "macintosh";
+    public static final String MIMENAME_SCSU = "SCSU";
+    public static final String MIMENAME_TIS_620 = "TIS-620";
+    public static final String MIMENAME_UTF_16BE = "UTF-16BE";
+    public static final String MIMENAME_UTF_16LE = "UTF-16LE";
+    public static final String MIMENAME_UTF_32 = "UTF-32";
+    public static final String MIMENAME_UTF_32BE = "UTF-32BE";
+    public static final String MIMENAME_UTF_32LE = "UTF-32LE";
+    public static final String MIMENAME_UTF_7 = "UTF-7";
+    public static final String MIMENAME_WINDOWS_1250 = "windows-1250";
+    public static final String MIMENAME_WINDOWS_1251 = "windows-1251";
+    public static final String MIMENAME_WINDOWS_1252 = "windows-1252";
+    public static final String MIMENAME_WINDOWS_1253 = "windows-1253";
+    public static final String MIMENAME_WINDOWS_1254 = "windows-1254";
+    public static final String MIMENAME_WINDOWS_1255 = "windows-1255";
+    public static final String MIMENAME_WINDOWS_1256 = "windows-1256";
+    public static final String MIMENAME_WINDOWS_1257 = "windows-1257";
+    public static final String MIMENAME_WINDOWS_1258 = "windows-1258";
+
     public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
 
     /**
@@ -116,6 +245,45 @@
         MIMENAME_BIG5,
         MIMENAME_UCS2,
         MIMENAME_UTF_16,
+        MIMENAME_BIG5_HKSCS,
+        MIMENAME_BOCU_1,
+        MIMENAME_CESU_8,
+        MIMENAME_CP864,
+        MIMENAME_EUC_JP,
+        MIMENAME_EUC_KR,
+        MIMENAME_GB18030,
+        MIMENAME_GBK,
+        MIMENAME_HZ_GB_2312,
+        MIMENAME_GB_2312,
+        MIMENAME_ISO_2022_CN,
+        MIMENAME_ISO_2022_CN_EXT,
+        MIMENAME_ISO_2022_JP,
+        MIMENAME_ISO_2022_KR,
+        MIMENAME_ISO_8859_10,
+        MIMENAME_ISO_8859_13,
+        MIMENAME_ISO_8859_14,
+        MIMENAME_ISO_8859_15,
+        MIMENAME_ISO_8859_16,
+        MIMENAME_KOI8_R,
+        MIMENAME_KOI8_U,
+        MIMENAME_MACINTOSH,
+        MIMENAME_SCSU,
+        MIMENAME_TIS_620,
+        MIMENAME_UTF_16BE,
+        MIMENAME_UTF_16LE,
+        MIMENAME_UTF_32,
+        MIMENAME_UTF_32BE,
+        MIMENAME_UTF_32LE,
+        MIMENAME_UTF_7,
+        MIMENAME_WINDOWS_1250,
+        MIMENAME_WINDOWS_1251,
+        MIMENAME_WINDOWS_1252,
+        MIMENAME_WINDOWS_1253,
+        MIMENAME_WINDOWS_1254,
+        MIMENAME_WINDOWS_1255,
+        MIMENAME_WINDOWS_1256,
+        MIMENAME_WINDOWS_1257,
+        MIMENAME_WINDOWS_1258,
     };
 
     private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
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/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e16fffa..e1aec0a 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.service.euicc.EuiccProfileInfo;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -168,7 +169,12 @@
                     new IGetAllProfilesCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccProfileInfo[] profiles) {
-                            executor.execute(() -> callback.onComplete(resultCode, profiles));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, profiles));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -192,7 +198,12 @@
                     new IGetProfileCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccProfileInfo profile) {
-                            executor.execute(() -> callback.onComplete(resultCode, profile));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, profile));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -217,7 +228,12 @@
                     refresh, new IDisableProfileCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -243,7 +259,12 @@
                     refresh, new ISwitchToProfileCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccProfileInfo profile) {
-                            executor.execute(() -> callback.onComplete(resultCode, profile));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, profile));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -268,7 +289,12 @@
                     nickname, new ISetNicknameCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -292,7 +318,12 @@
                     new IDeleteProfileCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -317,7 +348,12 @@
                     new IResetMemoryCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -340,7 +376,12 @@
                     new IGetDefaultSmdpAddressCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, String address) {
-                            executor.execute(() -> callback.onComplete(resultCode, address));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, address));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -363,7 +404,12 @@
                     new IGetSmdsAddressCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, String address) {
-                            executor.execute(() -> callback.onComplete(resultCode, address));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, address));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -388,7 +434,12 @@
                     new ISetDefaultSmdpAddressCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -411,7 +462,12 @@
                     new IGetRulesAuthTableCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccRulesAuthTable rat) {
-                            executor.execute(() -> callback.onComplete(resultCode, rat));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, rat));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -434,7 +490,12 @@
                     new IGetEuiccChallengeCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] challenge) {
-                            executor.execute(() -> callback.onComplete(resultCode, challenge));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, challenge));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -457,7 +518,12 @@
                     new IGetEuiccInfo1Callback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] info) {
-                            executor.execute(() -> callback.onComplete(resultCode, info));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, info));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -480,7 +546,12 @@
                     new IGetEuiccInfo2Callback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] info) {
-                            executor.execute(() -> callback.onComplete(resultCode, info));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, info));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -522,7 +593,12 @@
                     new IAuthenticateServerCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] response) {
-                            executor.execute(() -> callback.onComplete(resultCode, response));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, response));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -561,7 +637,12 @@
                     new IPrepareDownloadCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] response) {
-                            executor.execute(() -> callback.onComplete(resultCode, response));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, response));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -589,7 +670,12 @@
                     new ILoadBoundProfilePackageCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] response) {
-                            executor.execute(() -> callback.onComplete(resultCode, response));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, response));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -619,7 +705,12 @@
                     new ICancelSessionCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, byte[] response) {
-                            executor.execute(() -> callback.onComplete(resultCode, response));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, response));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -643,7 +734,13 @@
                     new IListNotificationsCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccNotification[] notifications) {
-                            executor.execute(() -> callback.onComplete(resultCode, notifications));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(
+                                        resultCode, notifications));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -667,7 +764,13 @@
                     events, new IRetrieveNotificationListCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccNotification[] notifications) {
-                            executor.execute(() -> callback.onComplete(resultCode, notifications));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(
+                                        resultCode, notifications));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -691,7 +794,13 @@
                     seqNumber, new IRetrieveNotificationCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode, EuiccNotification notification) {
-                            executor.execute(() -> callback.onComplete(resultCode, notification));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(
+                                        resultCode, notification));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
@@ -718,7 +827,12 @@
                     new IRemoveNotificationFromListCallback.Stub() {
                         @Override
                         public void onComplete(int resultCode) {
-                            executor.execute(() -> callback.onComplete(resultCode, null));
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, null));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
                         }
                     });
         } catch (RemoteException e) {
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/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/Android.bp b/tests/RollbackTest/Android.bp
index 98e7b4e..89005da 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -36,6 +36,15 @@
 }
 
 java_test_host {
+    name: "NetworkStagedRollbackTest",
+    srcs: ["NetworkStagedRollbackTest/src/**/*.java"],
+    libs: ["tradefed"],
+    static_libs: ["testng"],
+    test_suites: ["general-tests"],
+    test_config: "NetworkStagedRollbackTest.xml",
+}
+
+java_test_host {
     name: "MultiUserRollbackTest",
     srcs: ["MultiUserRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest.xml b/tests/RollbackTest/NetworkStagedRollbackTest.xml
new file mode 100644
index 0000000..a465a4f
--- /dev/null
+++ b/tests/RollbackTest/NetworkStagedRollbackTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs the network staged rollback tests">
+    <option name="test-suite-tag" value="NetworkStagedRollbackTest" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="RollbackTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.tests.rollback.host.NetworkStagedRollbackTest" />
+    </test>
+</configuration>
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
new file mode 100644
index 0000000..2c2e8282
--- /dev/null
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.rollback.host;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the network rollback tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class NetworkStagedRollbackTest extends BaseHostJUnit4Test {
+    /**
+     * Tests failed network health check triggers watchdog staged rollbacks.
+     */
+    @Test
+    public void testNetworkFailedRollback() throws Exception {
+    }
+
+    /**
+     * Tests passed network health check does not trigger watchdog staged rollbacks.
+     */
+    @Test
+    public void testNetworkPassedDoesNotRollback() throws Exception {
+    }
+}
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index a14b01c..f2c0f86 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -22,9 +22,9 @@
         <option name="package" value="com.android.tests.rollback" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
 
-        <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
-             specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
+        <!-- Exclude the device tests which need to be specially driven from the host tests -->
         <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
+        <option name="exclude-filter" value="com.android.tests.rollback.NetworkStagedRollbackTest" />
         <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
     </test>
 </configuration>
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
similarity index 71%
copy from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
copy to tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 3ad903b..04004d6 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -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.
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package com.android.tests.rollback;
 
-parcelable PhoneTimeZoneSuggestion;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NetworkStagedRollbackTest {
+}
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/Android.bp b/tests/net/Android.bp
index b2f384a..124b660 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -8,7 +8,6 @@
         "libbacktrace",
         "libbase",
         "libbinder",
-        "libbinderthreadstate",
         "libbpf",
         "libbpf_android",
         "libc++",
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/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 50c312c..968f552 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -24,6 +24,7 @@
 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;
@@ -106,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;
@@ -304,6 +306,7 @@
     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;
@@ -570,6 +573,9 @@
                 | 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;
@@ -577,6 +583,7 @@
         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();
@@ -649,7 +656,7 @@
 
             if (mNmValidationRedirectUrl != null) {
                 mNmCallbacks.showProvisioningNotification(
-                        "test_provisioning_notif_action", "com.android.test.package");
+                        "test_provisioning_notif_action", TEST_PACKAGE_NAME);
                 mNmProvNotificationRequested = true;
             }
         }
@@ -804,6 +811,11 @@
         public void expectPreventReconnectReceived() {
             expectPreventReconnectReceived(TIMEOUT_MS);
         }
+
+        void notifyDataStallSuspected() throws Exception {
+            mNmCallbacks.notifyDataStallSuspected(
+                    DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras);
+        }
     }
 
     /**
@@ -2962,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 {
@@ -3001,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);
         });
@@ -6436,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, 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));
@@ -6466,8 +6461,10 @@
         mService.registerConnectivityDiagnosticsCallback(
                 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(
@@ -6599,8 +6596,7 @@
         mServiceContext.setPermission(perm, PERMISSION_GRANTED);
     }
 
-    @Test
-    public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+    private void setUpConnectivityDiagnosticsCallback() throws Exception {
         final NetworkRequest request = new NetworkRequest.Builder().build();
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
@@ -6620,9 +6616,58 @@
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         callback.assertNoCallback();
+    }
 
-        // Wait for onConnectivityReport to fire
-        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+    @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/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 2af118c..5aa32f8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -391,6 +391,7 @@
   manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
   manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage);
   manifest_action["queries"]["intent"] = intent_filter_action;
+  manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities"));
   // TODO: more complicated component name tag
 
   manifest_action["key-sets"]["key-set"]["public-key"];
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index d3958a6..a251c05 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -30,7 +30,7 @@
         "utils.cpp",
     ],
     cflags: [
-        "-DSTATS_SCHEMA_LEGACY",
+        //"-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..1763975 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -47,7 +47,7 @@
         // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
         // to a separate package.
         "java/android/net/wifi/WifiNetworkScoreCache.java",
-        "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
+        "java/android/net/wifi/WifiOemMigrationHook.java",
         "java/android/net/wifi/wificond/*.java",
         ":libwificond_ipc_aidl",
     ],
@@ -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/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1f1c0c1..d4e024d 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -266,4 +266,10 @@
      * Return the Map of {@link WifiNetworkSuggestion} and the list of <ScanResult>
      */
     Map getMatchingScanResults(in List<WifiNetworkSuggestion> networkSuggestions, in List<ScanResult> scanResults, String callingPackage, String callingFeatureId);
+
+    void setScanThrottleEnabled(boolean enable);
+
+    boolean isScanThrottleEnabled();
+
+    Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult);
 }
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index c8fd243..0db3313 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -519,6 +519,9 @@
             case BAND_5GHZ:
                 wifiConfig.apBand  = WifiConfiguration.AP_BAND_5GHZ;
                 break;
+            case BAND_2GHZ | BAND_5GHZ:
+                wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
+                break;
             case BAND_ANY:
                 wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
                 break;
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 91b7df3..a720236 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;
         }
@@ -2085,7 +2112,8 @@
         return !TextUtils.isEmpty(FQDN)
                 && !TextUtils.isEmpty(providerFriendlyName)
                 && enterpriseConfig != null
-                && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+                && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE
+                && !TextUtils.isEmpty(mPasspointUniqueId);
     }
 
     /**
@@ -2467,12 +2495,17 @@
      */
     @NonNull
     public String getKey() {
-        String key = providerFriendlyName == null
-                ? getSsidAndSecurityTypeString()
-                : FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+        // Passpoint ephemeral networks have their unique identifier set. Return it as is to be
+        // able to match internally.
+        if (mPasspointUniqueId != null) {
+            return mPasspointUniqueId;
+        }
+
+        String key = getSsidAndSecurityTypeString();
         if (!shared) {
             key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier();
         }
+
         return key;
     }
 
@@ -2727,6 +2760,7 @@
             requirePMF = source.requirePMF;
             updateIdentifier = source.updateIdentifier;
             carrierId = source.carrierId;
+            mPasspointUniqueId = source.mPasspointUniqueId;
         }
     }
 
@@ -2799,6 +2833,7 @@
         dest.writeInt(osu ? 1 : 0);
         dest.writeLong(randomizedMacExpirationTimeMs);
         dest.writeInt(carrierId);
+        dest.writeString(mPasspointUniqueId);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -2873,6 +2908,7 @@
                 config.osu = in.readInt() != 0;
                 config.randomizedMacExpirationTimeMs = in.readLong();
                 config.carrierId = in.readInt();
+                config.mPasspointUniqueId = in.readString();
                 return config;
             }
 
@@ -2880,4 +2916,28 @@
                 return new WifiConfiguration[size];
             }
         };
+
+    /**
+     * Passpoint Unique identifier
+     * @hide
+     */
+    private String mPasspointUniqueId = null;
+
+    /**
+     * Set the Passpoint unique identifier
+     * @param uniqueId Passpoint unique identifier to be set
+     * @hide
+     */
+    public void setPasspointUniqueId(String uniqueId) {
+        mPasspointUniqueId = uniqueId;
+    }
+
+    /**
+     * Set the Passpoint unique identifier
+     * @hide
+     */
+    public String getPasspointUniqueId() {
+        return mPasspointUniqueId;
+    }
+
 }
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..0c306b4 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -291,6 +291,11 @@
      */
     private boolean mMeteredHint;
 
+    /**
+     * Passpoint unique key
+     */
+    private String mPasspointUniqueId;
+
     /** @hide */
     @UnsupportedAppUsage
     public WifiInfo() {
@@ -322,6 +327,7 @@
         setRequestingPackageName(null);
         setFQDN(null);
         setProviderFriendlyName(null);
+        setPasspointUniqueId(null);
         txBad = 0;
         txSuccess = 0;
         rxSuccess = 0;
@@ -370,6 +376,7 @@
             mWifiStandard = source.mWifiStandard;
             mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed;
             mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed;
+            mPasspointUniqueId = source.mPasspointUniqueId;
         }
     }
 
@@ -449,9 +456,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
@@ -978,6 +984,7 @@
         dest.writeInt(mWifiStandard);
         dest.writeInt(mMaxSupportedTxLinkSpeed);
         dest.writeInt(mMaxSupportedRxLinkSpeed);
+        dest.writeString(mPasspointUniqueId);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1022,6 +1029,7 @@
                 info.mWifiStandard = in.readInt();
                 info.mMaxSupportedTxLinkSpeed = in.readInt();
                 info.mMaxSupportedRxLinkSpeed = in.readInt();
+                info.mPasspointUniqueId = in.readString();
                 return info;
             }
 
@@ -1029,4 +1037,24 @@
                 return new WifiInfo[size];
             }
         };
+
+    /**
+     * Set the Passpoint unique identifier for the current connection
+     *
+     * @param passpointUniqueId Unique identifier
+     * @hide
+     */
+    public void setPasspointUniqueId(@Nullable String passpointUniqueId) {
+        mPasspointUniqueId = passpointUniqueId;
+    }
+
+    /**
+     * Get the Passpoint unique identifier for the current connection
+     *
+     * @return Passpoint unique identifier
+     * @hide
+     */
+    public @Nullable String getPasspointUniqueId() {
+        return mPasspointUniqueId;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1dc4a06..fb30910c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1400,8 +1400,7 @@
         List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>();
         try {
             Map<String, Map<Integer, List<ScanResult>>> results =
-                    mService.getAllMatchingFqdnsForScanResults(
-                            scanResults);
+                    mService.getAllMatchingPasspointProfilesForScanResults(scanResults);
             if (results.isEmpty()) {
                 return configs;
             }
@@ -1409,8 +1408,8 @@
                     mService.getWifiConfigsForPasspointProfiles(
                             new ArrayList<>(results.keySet()));
             for (WifiConfiguration configuration : wifiConfigurations) {
-                Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get(
-                        configuration.FQDN);
+                Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
+                        results.get(configuration.getKey());
                 if (scanResultsPerNetworkType != null) {
                     configs.add(Pair.create(configuration, scanResultsPerNetworkType));
                 }
@@ -1962,9 +1961,11 @@
      * for connecting to Passpoint networks that are operated by the Passpoint
      * service provider specified in the configuration.
      *
-     * Each configuration is uniquely identified by its FQDN (Fully Qualified Domain
-     * Name).  In the case when there is an existing configuration with the same
-     * FQDN, the new configuration will replace the existing configuration.
+     * Each configuration is uniquely identified by a unique key which depends on the contents of
+     * the configuration. This allows the caller to install multiple profiles with the same FQDN
+     * (Fully qualified domain name). Therefore, in order to update an existing profile, it is
+     * first required to remove it using {@link WifiManager#removePasspointConfiguration(String)}.
+     * Otherwise, a new profile will be added with both configuration.
      *
      * @param config The Passpoint configuration to be added
      * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
@@ -2625,8 +2626,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));
@@ -2982,7 +2983,7 @@
     }
 
     /**
-     * Start Soft AP (hotspot) mode with the specified configuration.
+     * Start Soft AP (hotspot) mode for tethering purposes with the specified configuration.
      * Note that starting Soft AP mode may disable station mode operation if the device does not
      * support concurrency.
      * @param wifiConfig SSID, security and channel details as part of WifiConfiguration, or null to
@@ -3278,7 +3279,7 @@
     }
 
     /**
-     * Gets the Wi-Fi enabled state.
+     * Gets the tethered Wi-Fi hotspot enabled state.
      * @return One of {@link #WIFI_AP_STATE_DISABLED},
      *         {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
      *         {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
@@ -3297,8 +3298,8 @@
     }
 
     /**
-     * Return whether Wi-Fi AP is enabled or disabled.
-     * @return {@code true} if Wi-Fi AP is enabled
+     * Return whether tethered Wi-Fi AP is enabled or disabled.
+     * @return {@code true} if tethered  Wi-Fi AP is enabled
      * @see #getWifiApState()
      *
      * @hide
@@ -3310,7 +3311,7 @@
     }
 
     /**
-     * Gets the Wi-Fi AP Configuration.
+     * Gets the tethered Wi-Fi AP Configuration.
      * @return AP details in WifiConfiguration
      *
      * Note that AP detail may contain configuration which is cannot be represented
@@ -3332,7 +3333,7 @@
     }
 
     /**
-     * Gets the Wi-Fi AP Configuration.
+     * Gets the Wi-Fi tethered AP Configuration.
      * @return AP details in {@link SoftApConfiguration}
      *
      * @hide
@@ -3349,7 +3350,7 @@
     }
 
     /**
-     * Sets the Wi-Fi AP Configuration.
+     * Sets the tethered Wi-Fi AP Configuration.
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
      * @deprecated This API is deprecated. Use {@link #setSoftApConfiguration(SoftApConfiguration)}
@@ -3368,9 +3369,9 @@
     }
 
     /**
-     * Sets the Wi-Fi AP Configuration.
+     * Sets the tethered Wi-Fi AP Configuration.
      *
-     * If the API is called while the soft AP is enabled, the configuration will apply to
+     * If the API is called while the tethered soft AP is enabled, the configuration will apply to
      * the current soft AP if the new configuration only includes
      * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
      * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
@@ -6118,4 +6119,48 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Enable/disable wifi scan throttling from 3rd party apps.
+     *
+     * <p>
+     * The throttling limits for apps are described in
+     * <a href="Wi-Fi Scan Throttling">
+     * https://developer.android.com/guide/topics/connectivity/wifi-scan#wifi-scan-throttling</a>
+     * </p>
+     *
+     * @param enable true to allow scan throttling, false to disallow scan throttling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void setScanThrottleEnabled(boolean enable) {
+        try {
+            mService.setScanThrottleEnabled(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the persisted Wi-Fi scan throttle state. Defaults to true, unless changed by the user via
+     * Developer options.
+     *
+     * <p>
+     * The throttling limits for apps are described in
+     * <a href="Wi-Fi Scan Throttling">
+     * https://developer.android.com/guide/topics/connectivity/wifi-scan#wifi-scan-throttling</a>
+     * </p>
+     *
+     * @return true to indicate that scan throttling is enabled, false to indicate that scan
+     * throttling is disabled.
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public boolean isScanThrottleEnabled() {
+        try {
+            return mService.isScanThrottleEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
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/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 7201496..a854a4b 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -569,6 +569,7 @@
         private WifiConfiguration buildWifiConfigurationForPasspoint() {
             WifiConfiguration wifiConfiguration = new WifiConfiguration();
             wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn();
+            wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId());
             wifiConfiguration.priority = mPriority;
             wifiConfiguration.meteredOverride =
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
@@ -804,7 +805,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
-                wifiConfiguration.allowedKeyManagement, wifiConfiguration.FQDN);
+                wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
     }
 
     /**
@@ -827,7 +828,8 @@
                 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                 lhs.wifiConfiguration.allowedKeyManagement)
-                && TextUtils.equals(this.wifiConfiguration.FQDN, lhs.wifiConfiguration.FQDN);
+                && TextUtils.equals(this.wifiConfiguration.getKey(),
+                lhs.wifiConfiguration.getKey());
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemMigrationHook.java
similarity index 71%
rename from wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
rename to wifi/java/android/net/wifi/WifiOemMigrationHook.java
index 642dcb9..22d7786 100755
--- a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
+++ b/wifi/java/android/net/wifi/WifiOemMigrationHook.java
@@ -28,30 +28,17 @@
 
 /**
  * Class used to provide one time hooks for existing OEM devices to migrate their config store
- * data to the wifi mainline module.
- * <p>
- * Note:
- * <li> OEM's need to implement {@link #load()} only if their
- * existing config store format or file locations differs from the vanilla AOSP implementation (
- * which is what the wifi mainline module understands).
- * </li>
- * <li> The wifi mainline module will invoke {@link #load()}  method on every bootup, its
- * the responsibility of the OEM implementation to ensure that this method returns non-null data
- * only on the first bootup. Once the migration is done, the OEM can safely delete their config
- * store files and then return null on any subsequent reboots. The first & only relevant invocation
- * of {@link #load()} occurs when a previously released device upgrades to the wifi
- * mainline module from an OEM implementation of the wifi stack.
- * </li>
+ * data and other settings to the wifi mainline module.
  * @hide
  */
 @SystemApi
-public final class WifiOemConfigStoreMigrationHook {
+public final class WifiOemMigrationHook {
     /**
      * Container for all the wifi config data to migrate.
      */
-    public static final class MigrationData implements Parcelable {
+    public static final class ConfigStoreMigrationData implements Parcelable {
         /**
-         * Builder to create instance of {@link MigrationData}.
+         * Builder to create instance of {@link ConfigStoreMigrationData}.
          */
         public static final class Builder {
             private List<WifiConfiguration> mUserSavedNetworkConfigurations;
@@ -92,39 +79,40 @@
             }
 
             /**
-             * Build an instance of {@link MigrationData}.
+             * Build an instance of {@link ConfigStoreMigrationData}.
              *
-             * @return Instance of {@link MigrationData}.
+             * @return Instance of {@link ConfigStoreMigrationData}.
              */
-            public @NonNull MigrationData build() {
-                return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+            public @NonNull ConfigStoreMigrationData build() {
+                return new ConfigStoreMigrationData(
+                        mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
             }
         }
 
         private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
         private final SoftApConfiguration mUserSoftApConfiguration;
 
-        private MigrationData(
+        private ConfigStoreMigrationData(
                 @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
                 @Nullable SoftApConfiguration userSoftApConfiguration) {
             mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
             mUserSoftApConfiguration = userSoftApConfiguration;
         }
 
-        public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
-                new Parcelable.Creator<MigrationData>() {
+        public static final @NonNull Parcelable.Creator<ConfigStoreMigrationData> CREATOR =
+                new Parcelable.Creator<ConfigStoreMigrationData>() {
                     @Override
-                    public MigrationData createFromParcel(Parcel in) {
+                    public ConfigStoreMigrationData createFromParcel(Parcel in) {
                         List<WifiConfiguration> userSavedNetworkConfigurations =
                                 in.readArrayList(null);
                         SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
-                        return new MigrationData(
+                        return new ConfigStoreMigrationData(
                                 userSavedNetworkConfigurations, userSoftApConfiguration);
                     }
 
                     @Override
-                    public MigrationData[] newArray(int size) {
-                        return new MigrationData[size];
+                    public ConfigStoreMigrationData[] newArray(int size) {
+                        return new ConfigStoreMigrationData[size];
                     }
                 };
 
@@ -164,16 +152,29 @@
         }
     }
 
-    private WifiOemConfigStoreMigrationHook() { }
+    private WifiOemMigrationHook() { }
 
     /**
      * Load data from OEM's config store.
+     * <p>
+     * Note:
+     * <li> OEM's need to implement {@link #loadFromConfigStore()} ()} only if their
+     * existing config store format or file locations differs from the vanilla AOSP implementation (
+     * which is what the wifi mainline module understands).
+     * </li>
+     * <li> The wifi mainline module will invoke {@link #loadFromConfigStore()} method on every
+     * bootup, its the responsibility of the OEM implementation to ensure that this method returns
+     * non-null data only on the first bootup. Once the migration is done, the OEM can safely delete
+     * their config store files and then return null on any subsequent reboots. The first & only
+     * relevant invocation of {@link #loadFromConfigStore()} occurs when a previously released
+     * device upgrades to the wifi mainline module from an OEM implementation of the wifi stack.
+     * </li>
      *
-     * @return Instance of {@link MigrationData} for migrating data, null if no
+     * @return Instance of {@link ConfigStoreMigrationData} for migrating data, null if no
      * migration is necessary.
      */
     @Nullable
-    public static MigrationData load() {
+    public static ConfigStoreMigrationData loadFromConfigStore() {
         // Note: OEM's should add code to parse data from their config store format here!
         return null;
     }
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/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 615331f..9f58184 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -908,6 +908,9 @@
             throw new IllegalStateException("Credential or HomeSP are not initialized");
         }
 
-        return mHomeSp.getFqdn();
+        StringBuilder sb = new StringBuilder();
+        sb.append(String.format("%s_%x%x", mHomeSp.getFqdn(), mHomeSp.hashCode(),
+                mCredential.hashCode()));
+        return sb.toString();
     }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 49a76c3..a5de331 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi.hotspot2.pps;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -299,8 +299,10 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFqdn, mFriendlyName, mIconUrl, mHomeNetworkIds, mMatchAllOis,
-                mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis);
+        return Objects.hash(mFqdn, mFriendlyName, mIconUrl,
+                mHomeNetworkIds, Arrays.hashCode(mMatchAllOis),
+                Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners),
+                Arrays.hashCode(mRoamingConsortiumOis));
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 36c7213..d479892 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -17,6 +17,7 @@
 package android.net.wifi.p2p;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -133,6 +134,7 @@
      *
      * By default this field is set to {@link #GROUP_OWNER_INTENT_AUTO}.
      */
+    @IntRange(from = 0, to = 15)
     public int groupOwnerIntent = GROUP_OWNER_INTENT_AUTO;
 
     /** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
index cdb2806..8a86311 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
@@ -22,7 +22,9 @@
 import android.os.Parcelable;
 import android.util.LruCache;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 
@@ -78,8 +80,8 @@
      * Get the list of P2P groups.
      */
     @NonNull
-    public Collection<WifiP2pGroup> getGroupList() {
-        return mGroups.snapshot().values();
+    public List<WifiP2pGroup> getGroupList() {
+        return new ArrayList<>(mGroups.snapshot().values());
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 0fe0675..9c2cad9 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -326,6 +326,12 @@
 
     /**
      * Broadcast intent action indicating that remembered persistent groups have changed.
+     *
+     * You can <em>not</em> receive this through components declared
+     * in manifests, only by explicitly registering for it with
+     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+     * android.content.IntentFilter) Context.registerReceiver()}.
+     *
      * @hide
      */
     @SystemApi
@@ -1347,20 +1353,33 @@
     }
 
     /**
-     * Force p2p to enter or exit listen state
+     * Force p2p to enter listen state
      *
      * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)}
-     * @param enable enables or disables listening
      * @param listener for callbacks on success or failure. Can be null.
      *
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public void listen(@NonNull Channel c, boolean enable, @Nullable ActionListener listener) {
+    public void startListening(@NonNull Channel c, @Nullable ActionListener listener) {
         checkChannel(c);
-        c.mAsyncChannel.sendMessage(enable ? START_LISTEN : STOP_LISTEN,
-                0, c.putListener(listener));
+        c.mAsyncChannel.sendMessage(START_LISTEN, 0, c.putListener(listener));
+    }
+
+    /**
+     * Force p2p to exit listen state
+     *
+     * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)}
+     * @param listener for callbacks on success or failure. Can be null.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void stopListening(@NonNull Channel c, @Nullable ActionListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(STOP_LISTEN, 0, c.putListener(listener));
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index 5484d24..e399b5b 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -17,6 +17,7 @@
 package android.net.wifi.p2p;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -34,7 +35,7 @@
  */
 public final class WifiP2pWfdInfo implements Parcelable {
 
-    private boolean mWfdEnabled;
+    private boolean mEnabled;
 
     /** Device information bitmap */
     private int mDeviceInfo;
@@ -85,15 +86,15 @@
     /** @hide */
     @UnsupportedAppUsage
     public WifiP2pWfdInfo(int devInfo, int ctrlPort, int maxTput) {
-        mWfdEnabled = true;
+        mEnabled = true;
         mDeviceInfo = devInfo;
         mCtrlPort = ctrlPort;
         mMaxThroughput = maxTput;
     }
 
     /** Returns true is Wifi Display is enabled, false otherwise. */
-    public boolean isWfdEnabled() {
-        return mWfdEnabled;
+    public boolean isEnabled() {
+        return mEnabled;
     }
 
     /**
@@ -101,8 +102,8 @@
      *
      * @param enabled true to enable Wifi Display, false to disable
      */
-    public void setWfdEnabled(boolean enabled) {
-        mWfdEnabled = enabled;
+    public void setEnabled(boolean enabled) {
+        mEnabled = enabled;
     }
 
     /**
@@ -177,12 +178,12 @@
     }
 
     /** Sets the TCP port at which the WFD Device listens for RTSP messages. */
-    public void setControlPort(int port) {
+    public void setControlPort(@IntRange(from = 0) int port) {
         mCtrlPort = port;
     }
 
     /** Sets the maximum average throughput capability of the WFD Device, in megabits/second. */
-    public void setMaxThroughput(int maxThroughput) {
+    public void setMaxThroughput(@IntRange(from = 0) int maxThroughput) {
         mMaxThroughput = maxThroughput;
     }
 
@@ -200,7 +201,7 @@
     @Override
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
-        sbuf.append("WFD enabled: ").append(mWfdEnabled);
+        sbuf.append("WFD enabled: ").append(mEnabled);
         sbuf.append("WFD DeviceInfo: ").append(mDeviceInfo);
         sbuf.append("\n WFD CtrlPort: ").append(mCtrlPort);
         sbuf.append("\n WFD MaxThroughput: ").append(mMaxThroughput);
@@ -215,7 +216,7 @@
     /** Copy constructor. */
     public WifiP2pWfdInfo(@Nullable WifiP2pWfdInfo source) {
         if (source != null) {
-            mWfdEnabled = source.mWfdEnabled;
+            mEnabled = source.mEnabled;
             mDeviceInfo = source.mDeviceInfo;
             mCtrlPort = source.mCtrlPort;
             mMaxThroughput = source.mMaxThroughput;
@@ -225,14 +226,14 @@
     /** Implement the Parcelable interface */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mWfdEnabled ? 1 : 0);
+        dest.writeInt(mEnabled ? 1 : 0);
         dest.writeInt(mDeviceInfo);
         dest.writeInt(mCtrlPort);
         dest.writeInt(mMaxThroughput);
     }
 
     private void readFromParcel(Parcel in) {
-        mWfdEnabled = (in.readInt() == 1);
+        mEnabled = (in.readInt() == 1);
         mDeviceInfo = in.readInt();
         mCtrlPort = in.readInt();
         mMaxThroughput = in.readInt();
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/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 2efdd97..d958488 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -287,17 +287,43 @@
 
     @Test
     public void testToWifiConfigurationWithSupportedParameter() {
-        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+        SoftApConfiguration softApConfig_2g = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(11, SoftApConfiguration.BAND_2GHZ)
+                .setHiddenSsid(true)
+                .build();
+        WifiConfiguration wifiConfig_2g = softApConfig_2g.toWifiConfiguration();
+        assertThat(wifiConfig_2g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_2g.preSharedKey).isEqualTo("secretsecret");
+        assertThat(wifiConfig_2g.apBand).isEqualTo(WifiConfiguration.AP_BAND_2GHZ);
+        assertThat(wifiConfig_2g.apChannel).isEqualTo(11);
+        assertThat(wifiConfig_2g.hiddenSSID).isEqualTo(true);
+
+        SoftApConfiguration softApConfig_5g = new SoftApConfiguration.Builder()
                 .setPassphrase("secretsecret",
                         SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .build();
-        WifiConfiguration wifiConfig = softApConfig.toWifiConfiguration();
-        assertThat(wifiConfig.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
-        assertThat(wifiConfig.preSharedKey).isEqualTo("secretsecret");
-        assertThat(wifiConfig.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ);
-        assertThat(wifiConfig.apChannel).isEqualTo(149);
-        assertThat(wifiConfig.hiddenSSID).isEqualTo(true);
+        WifiConfiguration wifiConfig_5g = softApConfig_5g.toWifiConfiguration();
+        assertThat(wifiConfig_5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_5g.preSharedKey).isEqualTo("secretsecret");
+        assertThat(wifiConfig_5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ);
+        assertThat(wifiConfig_5g.apChannel).isEqualTo(149);
+        assertThat(wifiConfig_5g.hiddenSSID).isEqualTo(true);
+
+        SoftApConfiguration softApConfig_2g5g = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        WifiConfiguration wifiConfig_2g5g = softApConfig_2g5g.toWifiConfiguration();
+        assertThat(wifiConfig_2g5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_2g5g.preSharedKey).isEqualTo("secretsecret");
+        assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY);
+        assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0);
+        assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true);
     }
 }
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..847040c 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -1551,14 +1551,15 @@
      */
     @Test
     public void testGetAllMatchingWifiConfigs() throws Exception {
-        Map<String, List<ScanResult>> fqdns = new HashMap<>();
-        fqdns.put("www.test.com", new ArrayList<>());
-        when(mWifiService.getAllMatchingFqdnsForScanResults(any(List.class))).thenReturn(fqdns);
+        Map<String, List<ScanResult>> passpointProfiles = new HashMap<>();
+        passpointProfiles.put("www.test.com_987a69bca26", new ArrayList<>());
+        when(mWifiService.getAllMatchingPasspointProfilesForScanResults(
+                any(List.class))).thenReturn(passpointProfiles);
         InOrder inOrder = inOrder(mWifiService);
 
         mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>());
 
-        inOrder.verify(mWifiService).getAllMatchingFqdnsForScanResults(any(List.class));
+        inOrder.verify(mWifiService).getAllMatchingPasspointProfilesForScanResults(any(List.class));
         inOrder.verify(mWifiService).getWifiConfigsForPasspointProfiles(any(List.class));
     }
 
@@ -1867,7 +1868,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 +1877,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);
     }
@@ -2380,4 +2381,14 @@
         verify(mWifiConnectedNetworkScorer).start(0);
         verify(mWifiConnectedNetworkScorer).stop(10);
     }
+
+    @Test
+    public void testScanThrottle() throws Exception {
+        mWifiManager.setScanThrottleEnabled(true);
+        verify(mWifiService).setScanThrottleEnabled(true);
+
+        when(mWifiService.isScanThrottleEnabled()).thenReturn(false);
+        assertFalse(mWifiManager.isScanThrottleEnabled());
+        verify(mWifiService).isScanThrottleEnabled();
+    }
 }
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/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index ce542c2..8f6beb1 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -20,8 +20,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -367,16 +370,55 @@
     }
 
     /**
-     * Verify that the unique identifier generated is correct.
+     * Verify that the unique identifier generated is identical for two instances
      *
      * @throws Exception
      */
     @Test
     public void validateUniqueId() throws Exception {
-        PasspointConfiguration config = PasspointTestUtils.createConfig();
-        String uniqueId;
-        uniqueId = config.getUniqueId();
-        assertEquals(uniqueId, config.getHomeSp().getFqdn());
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * HomeSp node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentHomeSp() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's RCOIs to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb});
+        config2.setHomeSp(homeSp);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's RCOIs to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        credential.getSimCredential().setImsi("350460*");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java
index 17ee755..6edc287 100644
--- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java
@@ -45,7 +45,7 @@
         assertEquals(devA.groupCapability, devB.groupCapability);
         assertEquals(devA.status, devB.status);
         if (devA.wfdInfo != null) {
-            assertEquals(devA.wfdInfo.isWfdEnabled(), devB.wfdInfo.isWfdEnabled());
+            assertEquals(devA.wfdInfo.isEnabled(), devB.wfdInfo.isEnabled());
             assertEquals(devA.wfdInfo.getDeviceInfoHex(), devB.wfdInfo.getDeviceInfoHex());
             assertEquals(devA.wfdInfo.getControlPort(), devB.wfdInfo.getControlPort());
             assertEquals(devA.wfdInfo.getMaxThroughput(), devB.wfdInfo.getMaxThroughput());
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
index 15a0aac..2a9b36b 100644
--- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
@@ -55,8 +55,8 @@
     public void testSettersGetters() throws Exception {
         WifiP2pWfdInfo info = new WifiP2pWfdInfo();
 
-        info.setWfdEnabled(true);
-        assertTrue(info.isWfdEnabled());
+        info.setEnabled(true);
+        assertTrue(info.isEnabled());
 
         info.setDeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
         assertEquals(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE, info.getDeviceType());
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();