Merge "People Service data layer main structure"
diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp
new file mode 100644
index 0000000..e813964
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/Android.bp
@@ -0,0 +1,56 @@
+// 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.
+
+android_test {
+    name: "LibStatsPullTests",
+    static_libs: [
+        "androidx.test.rules",
+        "platformprotoslite",
+        "statsdprotolite",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    jni_libs: [
+        "libstatspull_testhelper",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "protos/**/*.proto",
+        ],
+    test_suites: [
+        "general-tests",
+    ],
+    platform_apis: true,
+    privileged: true,
+    certificate: "platform",
+    compile_multilib: "both",
+}
+
+cc_library_shared {
+    name: "libstatspull_testhelper",
+    srcs: ["jni/stats_pull_helper.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "libstatspull",
+        "libstatssocket",
+    ],
+}
\ No newline at end of file
diff --git a/apex/statsd/tests/libstatspull/AndroidManifest.xml b/apex/statsd/tests/libstatspull/AndroidManifest.xml
new file mode 100644
index 0000000..bffd400
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.internal.os.statsd.libstats" >
+
+
+    <uses-permission android:name="android.permission.DUMP" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.internal.os.statsd.libstats"
+                     android:label="Tests for libstatspull">
+    </instrumentation>
+</manifest>
+
diff --git a/apex/statsd/tests/libstatspull/TEST_MAPPING b/apex/statsd/tests/libstatspull/TEST_MAPPING
new file mode 100644
index 0000000..5e1178cf
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "LibStatsPullTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
new file mode 100644
index 0000000..e4ab823
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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 <binder/ProcessState.h>
+#include <jni.h>
+#include <log/log.h>
+#include <stats_event.h>
+#include <stats_pull_atom_callback.h>
+
+#include <chrono>
+#include <thread>
+
+using std::this_thread::sleep_for;
+using namespace android;
+
+namespace {
+static int32_t sAtomTag;
+static int32_t sPullReturnVal;
+static int64_t sLatencyMillis;
+static int32_t sAtomsPerPull;
+static int32_t sNumPulls = 0;
+
+static bool initialized = false;
+
+static void init() {
+    if (!initialized) {
+        initialized = true;
+        // Set up the binder
+        sp<ProcessState> ps(ProcessState::self());
+        ps->setThreadPoolMaxThreadCount(9);
+        ps->startThreadPool();
+        ps->giveThreadPoolName();
+    }
+}
+
+static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* 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);
+    }
+    return sPullReturnVal;
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_registerStatsPuller(
+        JNIEnv* /*env*/, jobject /* this */, jint atomTag, jlong timeoutNs, jlong coolDownNs,
+        jint pullRetVal, jlong latencyMillis, int atomsPerPull)
+{
+    init();
+    sAtomTag = atomTag;
+    sPullReturnVal = pullRetVal;
+    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);
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller(
+        JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/)
+{
+    unregister_stats_pull_atom_callback(sAtomTag);
+}
+} // namespace
\ No newline at end of file
diff --git a/apex/statsd/tests/libstatspull/protos/test_atoms.proto b/apex/statsd/tests/libstatspull/protos/test_atoms.proto
new file mode 100644
index 0000000..56c1b53
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/protos/test_atoms.proto
@@ -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.
+ */
+syntax = "proto2";
+
+package com.android.internal.os.statsd.protos;
+
+option java_package = "com.android.internal.os.statsd.protos";
+option java_outer_classname = "TestAtoms";
+
+message PullCallbackAtomWrapper {
+  optional PullCallbackAtom pull_callback_atom = 150030;
+}
+
+message PullCallbackAtom {
+  optional int64 long_val = 1;
+}
+
+
+
diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java
new file mode 100644
index 0000000..d0d1400
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.os.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.StatsManager;
+import android.util.Log;
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.GaugeBucketInfo;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Util class for constructing statsd configs.
+ */
+public class StatsConfigUtils {
+    public static final String TAG = "statsd.StatsConfigUtils";
+    public static final int SHORT_WAIT = 2_000; // 2 seconds.
+
+    /**
+     * @return An empty StatsdConfig in serialized proto format.
+     */
+    public static StatsdConfig.Builder getSimpleTestConfig(long configId) {
+        return StatsdConfig.newBuilder().setId(configId)
+                .addAllowedLogSource(StatsConfigUtils.class.getPackage().getName());
+    }
+
+
+    public static boolean verifyValidConfigExists(StatsManager statsManager, long configId) {
+        StatsdStatsReport report = null;
+        try {
+            report = StatsdStatsReport.parser().parseFrom(statsManager.getStatsMetadata());
+        } catch (Exception e) {
+            Log.e(TAG, "getMetadata failed", e);
+        }
+        assertThat(report).isNotNull();
+        boolean foundConfig = false;
+        for (ConfigStats configStats : report.getConfigStatsList()) {
+            if (configStats.getId() == configId && configStats.getIsValid()
+                    && configStats.getDeletionTimeSec() == 0) {
+                foundConfig = true;
+            }
+        }
+        return foundConfig;
+    }
+
+    public static AtomMatcher getAppBreadcrumbMatcher(long id, int label) {
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(
+                        SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                        .setEqInt(label)
+                                )
+                )
+                .build();
+    }
+
+    public static ConfigMetricsReport getConfigMetricsReport(StatsManager statsManager,
+            long configId) {
+        ConfigMetricsReportList reportList = null;
+        try {
+            reportList = ConfigMetricsReportList.parser()
+                    .parseFrom(statsManager.getReports(configId));
+        } catch (Exception e) {
+            Log.e(TAG, "getData failed", e);
+        }
+        assertThat(reportList).isNotNull();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertThat(report.getDumpReportReason())
+                .isEqualTo(ConfigMetricsReport.DumpReportReason.GET_DATA_CALLED);
+        return report;
+
+    }
+    public static List<Atom> getGaugeMetricDataList(ConfigMetricsReport report) {
+        List<Atom> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            for (GaugeMetricData gaugeMetricData : metric.getGaugeMetrics().getDataList()) {
+                for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
+                    for (Atom atom : bucketInfo.getAtomList()) {
+                        data.add(atom);
+                    }
+                }
+            }
+        }
+        return data;
+    }
+
+    public static List<Atom> getGaugeMetricDataList(StatsManager statsManager, long configId) {
+        ConfigMetricsReport report = getConfigMetricsReport(statsManager, configId);
+        return getGaugeMetricDataList(report);
+    }
+}
+
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
new file mode 100644
index 0000000..dbd636d
--- /dev/null
+++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
@@ -0,0 +1,285 @@
+/*
+ * 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.internal.os.statsd.libstats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.util.Log;
+import android.util.StatsLog;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.statsd.StatsConfigUtils;
+import com.android.internal.os.statsd.protos.TestAtoms;
+import com.android.os.AtomsProto.Atom;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test puller registration.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LibStatsPullTests {
+    private static final String LOG_TAG = LibStatsPullTests.class.getSimpleName();
+    private static final int SHORT_SLEEP_MILLIS = 250;
+    private static final int LONG_SLEEP_MILLIS = 1_000;
+    private Context mContext;
+    private static final int PULL_ATOM_TAG = 150030;
+    private static final int APP_BREADCRUMB_LABEL = 3;
+    private static int sPullReturnValue;
+    private static long sConfigId;
+    private static long sPullLatencyMillis;
+    private static long sPullTimeoutNs;
+    private static long sCoolDownNs;
+    private static int sAtomsPerPull;
+
+    static {
+        System.loadLibrary("statspull_testhelper");
+    }
+
+    /**
+     * Setup the tests. Initialize shared data.
+     */
+    @Before
+    public void setup() {
+//        Debug.waitForDebugger();
+        mContext = InstrumentationRegistry.getTargetContext();
+        assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull();
+        sPullReturnValue = StatsManager.PULL_SUCCESS;
+        sPullLatencyMillis = 0;
+        sPullTimeoutNs = 10_000_000_000L;
+        sCoolDownNs = 1_000_000_000L;
+        sAtomsPerPull = 1;
+    }
+
+    /**
+     * Teardown the tests.
+     */
+    @After
+    public void tearDown() throws Exception {
+        unregisterStatsPuller(PULL_ATOM_TAG);
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        statsManager.removeConfig(sConfigId);
+    }
+
+    /**
+     * Tests adding a puller callback and that pulls complete successfully.
+     */
+    @Test
+    public void testPullAtomCallbackRegistration() throws Exception {
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        // Upload a config that captures that pulled atom.
+        createAndAddConfigToStatsd(statsManager);
+
+        // Add the puller.
+        registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue,
+                sPullLatencyMillis, sAtomsPerPull);
+        Thread.sleep(SHORT_SLEEP_MILLIS);
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        // Let the current bucket finish.
+        Thread.sleep(LONG_SLEEP_MILLIS);
+        List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
+        unregisterStatsPuller(PULL_ATOM_TAG);
+        assertThat(data.size()).isEqualTo(1);
+        TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
+        try {
+            atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
+                    .parseFrom(data.get(0).toByteArray());
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Failed to parse primitive atoms");
+        }
+        assertThat(atomWrapper).isNotNull();
+        assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
+        TestAtoms.PullCallbackAtom atom =
+                atomWrapper.getPullCallbackAtom();
+        assertThat(atom.getLongVal()).isEqualTo(1);
+    }
+
+    /**
+     * Tests that a failed pull is skipped.
+     */
+    @Test
+    public void testPullAtomCallbackFailure() throws Exception {
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        createAndAddConfigToStatsd(statsManager);
+        sPullReturnValue = StatsManager.PULL_SKIP;
+        // Add the puller.
+        registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue,
+                sPullLatencyMillis, sAtomsPerPull);
+        Thread.sleep(SHORT_SLEEP_MILLIS);
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        // Let the current bucket finish.
+        Thread.sleep(LONG_SLEEP_MILLIS);
+        List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
+        unregisterStatsPuller(PULL_ATOM_TAG);
+        assertThat(data.size()).isEqualTo(0);
+    }
+
+    /**
+     * Tests that a pull that times out is skipped.
+     */
+    @Test
+    public void testPullAtomCallbackTimeout() throws Exception {
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        createAndAddConfigToStatsd(statsManager);
+        // The puller will sleep for 1.5 sec.
+        sPullLatencyMillis = 1_500;
+        // 1 second timeout
+        sPullTimeoutNs = 1_000_000_000;
+
+        // Add the puller.
+        registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue,
+                sPullLatencyMillis, sAtomsPerPull);
+        Thread.sleep(SHORT_SLEEP_MILLIS);
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        // Let the current bucket finish and the pull timeout.
+        Thread.sleep(sPullLatencyMillis * 2);
+        List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
+        unregisterStatsPuller(PULL_ATOM_TAG);
+        assertThat(data.size()).isEqualTo(0);
+    }
+
+    /**
+     * Tests that 2 pulls in quick succession use the cache instead of pulling again.
+     */
+    @Test
+    public void testPullAtomCallbackCache() throws Exception {
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        createAndAddConfigToStatsd(statsManager);
+
+        // Set the cooldown to 10 seconds
+        sCoolDownNs = 10_000_000_000L;
+        // Add the puller.
+        registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue,
+                sPullLatencyMillis, sAtomsPerPull);
+
+        Thread.sleep(SHORT_SLEEP_MILLIS);
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        // Pull from cache.
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        Thread.sleep(LONG_SLEEP_MILLIS);
+        List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
+        unregisterStatsPuller(PULL_ATOM_TAG);
+        assertThat(data.size()).isEqualTo(2);
+        for (int i = 0; i < data.size(); i++) {
+            TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
+            try {
+                atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
+                        .parseFrom(data.get(i).toByteArray());
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Failed to parse primitive atoms");
+            }
+            assertThat(atomWrapper).isNotNull();
+            assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
+            TestAtoms.PullCallbackAtom atom =
+                    atomWrapper.getPullCallbackAtom();
+            assertThat(atom.getLongVal()).isEqualTo(1);
+        }
+    }
+
+    /**
+     * Tests that a pull that returns 1000 stats events works properly.
+     */
+    @Test
+    public void testPullAtomCallbackStress() throws Exception {
+        StatsManager statsManager = (StatsManager) mContext.getSystemService(
+                Context.STATS_MANAGER);
+        // Upload a config that captures that pulled atom.
+        createAndAddConfigToStatsd(statsManager);
+        sAtomsPerPull = 1000;
+        // Add the puller.
+        registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue,
+                sPullLatencyMillis, sAtomsPerPull);
+
+        Thread.sleep(SHORT_SLEEP_MILLIS);
+        StatsLog.logStart(APP_BREADCRUMB_LABEL);
+        // Let the current bucket finish.
+        Thread.sleep(LONG_SLEEP_MILLIS);
+        List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId);
+        statsManager.unregisterPullAtomCallback(PULL_ATOM_TAG);
+        assertThat(data.size()).isEqualTo(sAtomsPerPull);
+
+        for (int i = 0; i < data.size(); i++) {
+            TestAtoms.PullCallbackAtomWrapper atomWrapper = null;
+            try {
+                atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser()
+                        .parseFrom(data.get(i).toByteArray());
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Failed to parse primitive atoms");
+            }
+            assertThat(atomWrapper).isNotNull();
+            assertThat(atomWrapper.hasPullCallbackAtom()).isTrue();
+            TestAtoms.PullCallbackAtom atom =
+                    atomWrapper.getPullCallbackAtom();
+            assertThat(atom.getLongVal()).isEqualTo(1);
+        }
+    }
+
+    private void createAndAddConfigToStatsd(StatsManager statsManager) throws Exception {
+        sConfigId = System.currentTimeMillis();
+        long triggerMatcherId = sConfigId + 10;
+        long pullerMatcherId = sConfigId + 11;
+        long metricId = sConfigId + 100;
+        StatsdConfig config = StatsConfigUtils.getSimpleTestConfig(sConfigId)
+                .addAtomMatcher(
+                        StatsConfigUtils.getAppBreadcrumbMatcher(triggerMatcherId,
+                                APP_BREADCRUMB_LABEL))
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(pullerMatcherId)
+                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                                .setAtomId(PULL_ATOM_TAG))
+                )
+                .addGaugeMetric(GaugeMetric.newBuilder()
+                        .setId(metricId)
+                        .setWhat(pullerMatcherId)
+                        .setTriggerEvent(triggerMatcherId)
+                        .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true))
+                        .setBucket(TimeUnit.CTS)
+                        .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
+                        .setMaxNumGaugeAtomsPerBucket(1000)
+                )
+                .build();
+        statsManager.addConfig(sConfigId, config.toByteArray());
+        assertThat(StatsConfigUtils.verifyValidConfigExists(statsManager, sConfigId)).isTrue();
+    }
+
+    private native void registerStatsPuller(int atomTag, long timeoutNs, long coolDownNs,
+            int pullReturnVal, long latencyMillis, int atomPerPull);
+
+    private native void unregisterStatsPuller(int atomTag);
+}
+
diff --git a/api/current.txt b/api/current.txt
index 2ff8136..06271c8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43893,11 +43893,13 @@
     method public android.graphics.drawable.Icon getIcon();
     method public CharSequence getLabel();
     method public int getState();
+    method @Nullable public CharSequence getStateDescription();
     method @Nullable public CharSequence getSubtitle();
     method public void setContentDescription(CharSequence);
     method public void setIcon(android.graphics.drawable.Icon);
     method public void setLabel(CharSequence);
     method public void setState(int);
+    method public void setStateDescription(@Nullable CharSequence);
     method public void setSubtitle(@Nullable CharSequence);
     method public void updateTile();
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5ac00d0..1661c94 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -78,9 +78,9 @@
     // Pushed atoms start at 2.
     oneof pushed {
         // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
-        BleScanStateChanged ble_scan_state_changed = 2;
+        BleScanStateChanged ble_scan_state_changed = 2 [(module) = "bluetooth"];
         ProcessStateChanged process_state_changed = 3;
-        BleScanResultReceived ble_scan_result_received = 4;
+        BleScanResultReceived ble_scan_result_received = 4 [(module) = "bluetooth"];
         SensorStateChanged sensor_state_changed = 5;
         GpsScanStateChanged gps_scan_state_changed = 6;
         SyncStateChanged sync_state_changed = 7;
@@ -144,7 +144,8 @@
         AppDied app_died = 65;
         ResourceConfigurationChanged resource_configuration_changed = 66;
         BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
-        BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68;
+        BluetoothConnectionStateChanged bluetooth_connection_state_changed =
+                68 [(module) = "bluetooth"];
         GpsSignalQualityChanged gps_signal_quality_changed = 69;
         UsbConnectorStateChanged usb_connector_state_changed = 70;
         SpeakerImpedanceReported speaker_impedance_reported = 71;
@@ -207,9 +208,12 @@
         RescuePartyResetReported rescue_party_reset_reported = 122;
         SignedConfigReported signed_config_reported = 123;
         GnssNiEventReported gnss_ni_event_reported = 124;
-        BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
-        BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126;
-        BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127;
+        BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event =
+                125 [(module) = "bluetooth"];
+        BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed =
+                126 [(module) = "bluetooth"];
+        BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed =
+                127 [(module) = "bluetooth"];
         AppDowngraded app_downgraded = 128;
         AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129;
         LowStorageStateChanged low_storage_state_changed = 130;
@@ -233,23 +237,40 @@
         BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148;
         BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"];
         ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150;
-        BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151;
-        BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152;
-        BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = 153;
-        BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = 154;
-        BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = 155;
-        BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = 156;
-        BluetoothDeviceRssiReported bluetooth_device_rssi_reported = 157;
-        BluetoothDeviceFailedContactCounterReported bluetooth_device_failed_contact_counter_reported = 158;
-        BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = 159;
-        BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = 160;
-        BluetoothQualityReportReported bluetooth_quality_report_reported = 161;
-        BluetoothDeviceInfoReported bluetooth_device_info_reported = 162;
-        BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = 163;
-        BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = 164;
-        BluetoothBondStateChanged bluetooth_bond_state_changed = 165;
-        BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166;
-        BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167;
+        BluetoothActiveDeviceChanged bluetooth_active_device_changed =
+                151 [(module) = "bluetooth"];
+        BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed =
+                152 [(module) = "bluetooth"];
+        BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed =
+                153 [(module) = "bluetooth"];
+        BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed =
+                154 [(module) = "bluetooth"];
+        BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported =
+                155 [(module) = "bluetooth"];
+        BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported =
+                156 [(module) = "bluetooth"];
+        BluetoothDeviceRssiReported bluetooth_device_rssi_reported =
+                157 [(module) = "bluetooth"];
+        BluetoothDeviceFailedContactCounterReported
+                bluetooth_device_failed_contact_counter_reported = 158 [(module) = "bluetooth"];
+        BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported =
+                159 [(module) = "bluetooth"];
+        BluetoothHciTimeoutReported bluetooth_hci_timeout_reported =
+                160 [(module) = "bluetooth"];
+        BluetoothQualityReportReported bluetooth_quality_report_reported =
+                161 [(module) = "bluetooth"];
+        BluetoothDeviceInfoReported bluetooth_device_info_reported =
+                162 [(module) = "bluetooth"];
+        BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported =
+                163 [(module) = "bluetooth"];
+        BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported =
+                164 [(module) = "bluetooth"];
+        BluetoothBondStateChanged bluetooth_bond_state_changed =
+                165 [(module) = "bluetooth"];
+        BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported =
+                166 [(module) = "bluetooth"];
+        BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported =
+                167 [(module) = "bluetooth"];
         ScreenTimeoutExtensionReported screen_timeout_extension_reported = 168;
         ProcessStartTime process_start_time = 169;
         PermissionGrantRequestResultReported permission_grant_request_result_reported =
@@ -272,7 +293,8 @@
         BiometricEnrolled biometric_enrolled = 184;
         SystemServerWatchdogOccurred system_server_watchdog_occurred = 185;
         TombStoneOccurred tomb_stone_occurred = 186;
-        BluetoothClassOfDeviceReported bluetooth_class_of_device_reported = 187;
+        BluetoothClassOfDeviceReported bluetooth_class_of_device_reported =
+                187 [(module) = "bluetooth"];
         IntelligenceEventReported intelligence_event_reported =
             188 [(module) = "intelligence"];
         ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = 189;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cdb49f0..35d26ab 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3569,8 +3569,16 @@
          * This field will be ignored by Launchers that don't support badging, don't show
          * notification content, or don't show {@link android.content.pm.ShortcutManager shortcuts}.
          *
+         * If this notification has {@link BubbleMetadata} attached that was created with
+         * {@link BubbleMetadata.Builder#createShortcutBubble(String)} a check will be performed
+         * to ensure the shortcutId supplied to bubble metadata matches the shortcutId set here,
+         * if one was set. If the shortcutId's were specified but do not match, an exception
+         * is thrown.
+         *
          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
          *                   supersedes
+         *
+         * @see BubbleMetadata.Builder#createShortcutBubble(String)
          */
         @NonNull
         public Builder setShortcutId(String shortcutId) {
@@ -5926,9 +5934,29 @@
         /**
          * Combine all of the options that have been set and return a new {@link Notification}
          * object.
+         *
+         * If this notification has {@link BubbleMetadata} attached that was created with
+         * {@link BubbleMetadata.Builder#createShortcutBubble(String)} a check will be performed
+         * to ensure the shortcutId supplied to bubble metadata matches the shortcutId set on the
+         * notification builder, if one was set. If the shortcutId's were specified but do not
+         * match, an exception is thrown here.
+         *
+         * @see BubbleMetadata.Builder#createShortcutBubble(String)
+         * @see #setShortcutId(String)
          */
         @NonNull
         public Notification build() {
+            // Check shortcut id matches
+            if (mN.mShortcutId != null
+                    && mN.mBubbleMetadata != null
+                    && mN.mBubbleMetadata.getShortcutId() != null
+                    && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
+                throw new IllegalArgumentException(
+                        "Notification and BubbleMetadata shortcut id's don't match,"
+                                + " notification: " + mN.mShortcutId
+                                + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
+            }
+
             // first, add any extras from the calling code
             if (mUserExtras != null) {
                 mN.extras = getAllExtras();
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 9333dbd..36f3a78 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -748,8 +748,8 @@
         parcel.writeParcelableArray(mFieldClassificationIds, flags);
         parcel.writeInt(mFlags);
         parcel.writeIntArray(mCancelIds);
-        parcel.writeInt(mRequestId);
         parcel.writeParcelable(mInlineActions, flags);
+        parcel.writeInt(mRequestId);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<FillResponse> CREATOR =
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index 79c2152..40c0ac0 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -65,6 +65,7 @@
     private CharSequence mLabel;
     private CharSequence mSubtitle;
     private CharSequence mContentDescription;
+    private CharSequence mStateDescription;
     // Default to inactive until clients of the new API can update.
     private int mState = STATE_INACTIVE;
 
@@ -177,6 +178,14 @@
     }
 
     /**
+     * Gets the current state description for the tile.
+     */
+    @Nullable
+    public CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
      * Sets the current content description for the tile.
      *
      * Does not take effect until {@link #updateTile()} is called.
@@ -187,6 +196,17 @@
         this.mContentDescription = contentDescription;
     }
 
+    /**
+     * Sets the current state description for the tile.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param stateDescription New state description to use.
+     */
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        this.mStateDescription = stateDescription;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -215,6 +235,7 @@
         TextUtils.writeToParcel(mLabel, dest, flags);
         TextUtils.writeToParcel(mSubtitle, dest, flags);
         TextUtils.writeToParcel(mContentDescription, dest, flags);
+        TextUtils.writeToParcel(mStateDescription, dest, flags);
     }
 
     private void readFromParcel(Parcel source) {
@@ -227,6 +248,7 @@
         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mStateDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
     }
 
     public static final @android.annotation.NonNull Creator<Tile> CREATOR = new Creator<Tile>() {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4f1125f..007d367 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -314,7 +314,7 @@
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     return env->NewObject(
-            env->FindClass("android/media/tv/tuner/Tuner$Lnb"),
+            env->FindClass("android/media/tv/tuner/Lnb"),
             gFields.lnbInitID,
             mObject,
             id);
@@ -373,7 +373,7 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jobject descramblerObj =
             env->NewObject(
-                    env->FindClass("android/media/tv/tuner/Tuner$Descrambler"),
+                    env->FindClass("android/media/tv/tuner/Descrambler"),
                     gFields.descramblerInitID,
                     mObject);
 
@@ -408,7 +408,7 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jobject filterObj =
             env->NewObject(
-                    env->FindClass("android/media/tv/tuner/Tuner$Filter"),
+                    env->FindClass("android/media/tv/tuner/filter/Filter"),
                     gFields.filterInitID,
                     mObject,
                     (jint) fId);
@@ -443,7 +443,7 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jobject dvrObj =
             env->NewObject(
-                    env->FindClass("android/media/tv/tuner/Tuner$Dvr"),
+                    env->FindClass("android/media/tv/tuner/dvr/Dvr"),
                     gFields.dvrInitID,
                     mObject);
     sp<Dvr> dvrSp = new Dvr(iDvrSp, dvrObj);
@@ -495,14 +495,14 @@
 
 static FrontendSettings getFrontendSettings(JNIEnv *env, int type, jobject settings) {
     FrontendSettings frontendSettings;
-    jclass clazz = env->FindClass("android/media/tv/tuner/FrontendSettings");
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendSettings");
     jfieldID freqField = env->GetFieldID(clazz, "mFrequency", "I");
     uint32_t freq = static_cast<uint32_t>(env->GetIntField(clazz, freqField));
 
     // TODO: handle the other 8 types of settings
     if (type == 1) {
         // analog
-        clazz = env->FindClass("android/media/tv/tuner/FrontendSettings$FrontendAnalogSettings");
+        clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendSettings");
         FrontendAnalogType analogType =
                 static_cast<FrontendAnalogType>(
                         env->GetIntField(settings, env->GetFieldID(clazz, "mAnalogType", "I")));
@@ -525,7 +525,7 @@
 
 static DvrSettings getDvrSettings(JNIEnv *env, jobject settings) {
     DvrSettings dvrSettings;
-    jclass clazz = env->FindClass("android/media/tv/tuner/DvrSettings");
+    jclass clazz = env->FindClass("android/media/tv/tuner/dvr/DvrSettings");
     uint32_t statusMask =
             static_cast<uint32_t>(env->GetIntField(
                     settings, env->GetFieldID(clazz, "mStatusMask", "I")));
@@ -585,23 +585,23 @@
     gFields.frontendInitID =
             env->GetMethodID(frontendClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V");
 
-    jclass lnbClazz = env->FindClass("android/media/tv/tuner/Tuner$Lnb");
+    jclass lnbClazz = env->FindClass("android/media/tv/tuner/Lnb");
     gFields.lnbInitID =
             env->GetMethodID(lnbClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V");
 
-    jclass filterClazz = env->FindClass("android/media/tv/tuner/Tuner$Filter");
+    jclass filterClazz = env->FindClass("android/media/tv/tuner/filter/Filter");
     gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
     gFields.filterInitID =
             env->GetMethodID(filterClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V");
     gFields.onFilterStatusID =
             env->GetMethodID(filterClazz, "onFilterStatus", "(I)V");
 
-    jclass descramblerClazz = env->FindClass("android/media/tv/tuner/Tuner$Descrambler");
+    jclass descramblerClazz = env->FindClass("android/media/tv/tuner/Descrambler");
     gFields.descramblerContext = env->GetFieldID(descramblerClazz, "mNativeContext", "J");
     gFields.descramblerInitID =
             env->GetMethodID(descramblerClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;)V");
 
-    jclass dvrClazz = env->FindClass("android/media/tv/tuner/Tuner$Dvr");
+    jclass dvrClazz = env->FindClass("android/media/tv/tuner/dvr/Dvr");
     gFields.dvrContext = env->GetFieldID(dvrClazz, "mNativeContext", "J");
     gFields.dvrInitID = env->GetMethodID(dvrClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;)V");
 }
@@ -649,7 +649,7 @@
     return 0;
 }
 
-static jobjectArray android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
+static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
     return NULL;
 }
 
@@ -684,7 +684,7 @@
 }
 
 static jobject android_media_tv_Tuner_open_filter(
-        JNIEnv *env, jobject thiz, jint type, jint subType, jint bufferSize) {
+        JNIEnv *env, jobject thiz, jint type, jint subType, jlong bufferSize) {
     sp<JTuner> tuner = getTuner(env, thiz);
     DemuxFilterType filterType {
         .mainType = static_cast<DemuxFilterMainType>(type),
@@ -708,17 +708,17 @@
             env->GetObjectField(
                     filterSettingsObj,
                     env->GetFieldID(
-                            env->FindClass("android/media/tv/tuner/FilterSettings"),
+                            env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"),
                             "mSettings",
-                            "Landroid/media/tv/tuner/FilterSettings$Settings;"));
+                            "Landroid/media/tv/tuner/filter/Settings;"));
     if (type == (int)DemuxFilterMainType::TS) {
         // DemuxTsFilterSettings
-        jclass clazz = env->FindClass("android/media/tv/tuner/FilterSettings$TsFilterSettings");
+        jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
         int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I"));
         if (subtype == (int)DemuxTsFilterType::PES) {
             // DemuxFilterPesDataSettings
             jclass settingClazz =
-                    env->FindClass("android/media/tv/tuner/FilterSettings$PesSettings");
+                    env->FindClass("android/media/tv/tuner/filter/PesSettings");
             int streamId = env->GetIntField(
                     settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I"));
             bool isRaw = (bool)env->GetBooleanField(
@@ -831,7 +831,7 @@
 }
 
 static int android_media_tv_Tuner_read_filter_fmq(
-        JNIEnv *env, jobject filter, jbyteArray buffer, jint offset, jint size) {
+        JNIEnv *env, jobject filter, jbyteArray buffer, jlong offset, jlong size) {
     sp<Filter> filterSp = getFilter(env, filter);
     if (filterSp == NULL) {
         ALOGD("Failed to read filter FMQ: filter not found");
@@ -901,9 +901,14 @@
     return 0;
 }
 
-static jobject android_media_tv_Tuner_open_dvr(JNIEnv *env, jobject thiz, jint type, jint bufferSize) {
-    sp<JTuner> tuner = getTuner(env, thiz);
-    return tuner->openDvr(static_cast<DvrType>(type), bufferSize);
+static jobject android_media_tv_Tuner_open_dvr_recorder(
+        JNIEnv* /* env */, jobject /* thiz */, jlong /* bufferSize */) {
+    return NULL;
+}
+
+static jobject android_media_tv_Tuner_open_dvr_playback(
+        JNIEnv* /* env */, jobject /* thiz */, jlong /* bufferSize */) {
+    return NULL;
 }
 
 static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) {
@@ -1019,35 +1024,35 @@
     ALOGD("set fd = %d", dvrSp->mFd);
 }
 
-static int android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jint size) {
+static jlong android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jlong size) {
     sp<Dvr> dvrSp = getDvr(env, dvr);
     if (dvrSp == NULL) {
         ALOGD("Failed to read dvr: dvr not found");
     }
 
-    int available = dvrSp->mDvrMQ->availableToWrite();
-    int write = std::min(size, available);
+    long available = dvrSp->mDvrMQ->availableToWrite();
+    long write = std::min((long) size, available);
 
     DvrMQ::MemTransaction tx;
-    int ret = 0;
+    long ret = 0;
     if (dvrSp->mDvrMQ->beginWrite(write, &tx)) {
         auto first = tx.getFirstRegion();
         auto data = first.getAddress();
-        int length = first.getLength();
-        int firstToWrite = std::min(length, write);
+        long length = first.getLength();
+        long firstToWrite = std::min(length, write);
         ret = read(dvrSp->mFd, data, firstToWrite);
         if (ret < firstToWrite) {
-            ALOGW("[DVR] file to MQ, first region: %d bytes to write, but %d bytes written",
+            ALOGW("[DVR] file to MQ, first region: %ld bytes to write, but %ld bytes written",
                     firstToWrite, ret);
         } else if (firstToWrite < write) {
-            ALOGD("[DVR] write second region: %d bytes written, %d bytes in total", ret, write);
+            ALOGD("[DVR] write second region: %ld bytes written, %ld bytes in total", ret, write);
             auto second = tx.getSecondRegion();
             data = second.getAddress();
             length = second.getLength();
             int secondToWrite = std::min(length, write - firstToWrite);
             ret += read(dvrSp->mFd, data, secondToWrite);
         }
-        ALOGD("[DVR] file to MQ: %d bytes need to be written, %d bytes written", write, ret);
+        ALOGD("[DVR] file to MQ: %ld bytes need to be written, %ld bytes written", write, ret);
         if (!dvrSp->mDvrMQ->commitWrite(ret)) {
             ALOGE("[DVR] Error: failed to commit write!");
         }
@@ -1055,17 +1060,17 @@
     } else {
         ALOGE("dvrMq.beginWrite failed");
     }
-    return ret;
+    return (jlong) ret;
 }
 
-static int android_media_tv_Tuner_read_dvr_from_array(
-        JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */,
-        jint /* size */) {
+static jlong android_media_tv_Tuner_read_dvr_from_array(
+        JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jlong /* offset */,
+        jlong /* size */) {
     //TODO: impl
     return 0;
 }
 
-static int android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jint size) {
+static jlong android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jlong size) {
     sp<Dvr> dvrSp = getDvr(env, dvr);
     if (dvrSp == NULL) {
         ALOGW("Failed to write dvr: dvr not found");
@@ -1079,28 +1084,28 @@
 
     DvrMQ& dvrMq = dvrSp->getDvrMQ();
 
-    int available = dvrMq.availableToRead();
-    int toRead = std::min(size, available);
+    long available = dvrMq.availableToRead();
+    long toRead = std::min((long) size, available);
 
-    int ret = 0;
+    long ret = 0;
     DvrMQ::MemTransaction tx;
     if (dvrMq.beginRead(toRead, &tx)) {
         auto first = tx.getFirstRegion();
         auto data = first.getAddress();
-        int length = first.getLength();
-        int firstToRead = std::min(length, toRead);
+        long length = first.getLength();
+        long firstToRead = std::min(length, toRead);
         ret = write(dvrSp->mFd, data, firstToRead);
         if (ret < firstToRead) {
-            ALOGW("[DVR] MQ to file: %d bytes read, but %d bytes written", firstToRead, ret);
+            ALOGW("[DVR] MQ to file: %ld bytes read, but %ld bytes written", firstToRead, ret);
         } else if (firstToRead < toRead) {
-            ALOGD("[DVR] read second region: %d bytes read, %d bytes in total", ret, toRead);
+            ALOGD("[DVR] read second region: %ld bytes read, %ld bytes in total", ret, toRead);
             auto second = tx.getSecondRegion();
             data = second.getAddress();
             length = second.getLength();
             int secondToRead = toRead - firstToRead;
             ret += write(dvrSp->mFd, data, secondToRead);
         }
-        ALOGD("[DVR] MQ to file: %d bytes to be read, %d bytes written", toRead, ret);
+        ALOGD("[DVR] MQ to file: %ld bytes to be read, %ld bytes written", toRead, ret);
         if (!dvrMq.commitRead(ret)) {
             ALOGE("[DVR] Error: failed to commit read!");
         }
@@ -1109,12 +1114,12 @@
         ALOGE("dvrMq.beginRead failed");
     }
 
-    return ret;
+    return (jlong) ret;
 }
 
-static int android_media_tv_Tuner_write_dvr_to_array(
-        JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */,
-        jint /* size */) {
+static jlong android_media_tv_Tuner_write_dvr_to_array(
+        JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jlong /* offset */,
+        jlong /* size */) {
     //TODO: impl
     return 0;
 }
@@ -1126,49 +1131,51 @@
             (void *)android_media_tv_Tuner_get_frontend_ids },
     { "nativeOpenFrontendById", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
             (void *)android_media_tv_Tuner_open_frontend_by_id },
-    { "nativeTune", "(ILandroid/media/tv/tuner/FrontendSettings;)I",
+    { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
-    { "nativeScan", "(ILandroid/media/tv/tuner/FrontendSettings;I)I",
+    { "nativeScan", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;I)I",
             (void *)android_media_tv_Tuner_scan },
     { "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan },
     { "nativeSetLnb", "(I)I", (void *)android_media_tv_Tuner_set_lnb },
     { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
-    { "nativeGetFrontendStatus", "([I)[Landroid/media/tv/tuner/FrontendStatus;",
+    { "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
             (void *)android_media_tv_Tuner_get_frontend_status },
-    { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/Tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_gat_av_sync_hw_id },
     { "nativeGetAvSyncTime", "(I)J", (void *)android_media_tv_Tuner_gat_av_sync_time },
     { "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam },
     { "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam },
-    { "nativeGetFrontendInfo", "(I)[Landroid/media/tv/tuner/FrontendInfo;",
+    { "nativeGetFrontendInfo", "(I)Landroid/media/tv/tuner/FrontendInfo;",
             (void *)android_media_tv_Tuner_get_frontend_info },
-    { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
+    { "nativeOpenFilter", "(IIJ)Landroid/media/tv/tuner/Tuner/filter/Filter;",
             (void *)android_media_tv_Tuner_open_filter },
-    { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;",
+    { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner/filter/TimeFilter;",
             (void *)android_media_tv_Tuner_open_time_filter },
     { "nativeGetLnbIds", "()Ljava/util/List;",
             (void *)android_media_tv_Tuner_get_lnb_ids },
-    { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;",
+    { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Lnb;",
             (void *)android_media_tv_Tuner_open_lnb_by_id },
-    { "nativeOpenDescrambler", "()Landroid/media/tv/tuner/Tuner$Descrambler;",
+    { "nativeOpenDescrambler", "()Landroid/media/tv/tuner/Descrambler;",
             (void *)android_media_tv_Tuner_open_descrambler },
-    { "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;",
-            (void *)android_media_tv_Tuner_open_dvr },
+    { "nativeOpenDvrRecorder", "(J)Landroid/media/tv/tuner/dvr/DvrRecorder;",
+            (void *)android_media_tv_Tuner_open_dvr_recorder },
+    { "nativeOpenDvrPlayback", "(J)Landroid/media/tv/tuner/dvr/DvrPlayback;",
+            (void *)android_media_tv_Tuner_open_dvr_playback },
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
-    { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/FilterSettings;)I",
+    { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
             (void *)android_media_tv_Tuner_configure_filter },
     { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
-    { "nativeSetDataSource", "(Landroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_set_filter_data_source },
     { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
     { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter },
     { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
-    { "nativeRead", "([BII)I", (void *)android_media_tv_Tuner_read_filter_fmq },
+    { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
 };
 
@@ -1183,31 +1190,36 @@
 };
 
 static const JNINativeMethod gDescramblerMethods[] = {
-    { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeAddPid", "(IILandroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_add_pid },
-    { "nativeRemovePid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeRemovePid", "(IILandroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_remove_pid },
     { "nativeSetKeyToken", "([B)I", (void *)android_media_tv_Tuner_set_key_token },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_descrambler },
 };
 
 static const JNINativeMethod gDvrMethods[] = {
-    { "nativeAttachFilter", "(Landroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeAttachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_attach_filter },
-    { "nativeDetachFilter", "(Landroid/media/tv/tuner/Tuner$Filter;)I",
+    { "nativeDetachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_detach_filter },
-    { "nativeConfigureDvr", "(Landroid/media/tv/tuner/DvrSettings;)I",
+    { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
             (void *)android_media_tv_Tuner_configure_dvr },
     { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
     { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
     { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr },
-    { "nativeSetFileDescriptor", "(Ljava/io/FileDescriptor;)V",
-            (void *)android_media_tv_Tuner_dvr_set_fd },
-    { "nativeRead", "(I)I", (void *)android_media_tv_Tuner_read_dvr },
-    { "nativeRead", "([BII)I", (void *)android_media_tv_Tuner_read_dvr_from_array },
-    { "nativeWrite", "(I)I", (void *)android_media_tv_Tuner_write_dvr },
-    { "nativeWrite", "([BII)I", (void *)android_media_tv_Tuner_write_dvr_to_array },
+    { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd },
+};
+
+static const JNINativeMethod gDvrRecorderMethods[] = {
+    { "nativeWrite", "(J)J", (void *)android_media_tv_Tuner_write_dvr },
+    { "nativeWrite", "([BJJ)J", (void *)android_media_tv_Tuner_write_dvr_to_array },
+};
+
+static const JNINativeMethod gDvrPlaybackMethods[] = {
+    { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr },
+    { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array },
 };
 
 static const JNINativeMethod gLnbMethods[] = {
@@ -1225,35 +1237,49 @@
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
-            env, "android/media/tv/tuner/Tuner$Filter",
+            env, "android/media/tv/tuner/filter/Filter",
             gFilterMethods,
             NELEM(gFilterMethods)) != JNI_OK) {
         ALOGE("Failed to register filter native methods");
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
-            env, "android/media/tv/tuner/Tuner$TimeFilter",
+            env, "android/media/tv/tuner/filter/TimeFilter",
             gTimeFilterMethods,
             NELEM(gTimeFilterMethods)) != JNI_OK) {
         ALOGE("Failed to register time filter native methods");
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
-            env, "android/media/tv/tuner/Tuner$Descrambler",
+            env, "android/media/tv/tuner/Descrambler",
             gDescramblerMethods,
             NELEM(gDescramblerMethods)) != JNI_OK) {
         ALOGE("Failed to register descrambler native methods");
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
-            env, "android/media/tv/tuner/Tuner$Dvr",
+            env, "android/media/tv/tuner/dvr/Dvr",
             gDvrMethods,
             NELEM(gDvrMethods)) != JNI_OK) {
         ALOGE("Failed to register dvr native methods");
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
-            env, "android/media/tv/tuner/Tuner$Lnb",
+            env, "android/media/tv/tuner/dvr/DvrRecorder",
+            gDvrRecorderMethods,
+            NELEM(gDvrRecorderMethods)) != JNI_OK) {
+        ALOGE("Failed to register dvr recorder native methods");
+        return false;
+    }
+    if (AndroidRuntime::registerNativeMethods(
+            env, "android/media/tv/tuner/dvr/DvrPlayback",
+            gDvrPlaybackMethods,
+            NELEM(gDvrPlaybackMethods)) != JNI_OK) {
+        ALOGE("Failed to register dvr playback native methods");
+        return false;
+    }
+    if (AndroidRuntime::registerNativeMethods(
+            env, "android/media/tv/tuner/Lnb",
             gLnbMethods,
             NELEM(gLnbMethods)) != JNI_OK) {
         ALOGE("Failed to register lnb native methods");
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 6518924..17f2f47 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -133,6 +133,7 @@
         public CharSequence label;
         public CharSequence secondaryLabel;
         public CharSequence contentDescription;
+        public CharSequence stateDescription;
         public CharSequence dualLabelContentDescription;
         public boolean disabledByPolicy;
         public boolean dualTarget = false;
@@ -151,6 +152,7 @@
                     || !Objects.equals(other.label, label)
                     || !Objects.equals(other.secondaryLabel, secondaryLabel)
                     || !Objects.equals(other.contentDescription, contentDescription)
+                    || !Objects.equals(other.stateDescription, stateDescription)
                     || !Objects.equals(other.dualLabelContentDescription,
                             dualLabelContentDescription)
                     || !Objects.equals(other.expandedAccessibilityClassName,
@@ -168,6 +170,7 @@
             other.label = label;
             other.secondaryLabel = secondaryLabel;
             other.contentDescription = contentDescription;
+            other.stateDescription = stateDescription;
             other.dualLabelContentDescription = dualLabelContentDescription;
             other.expandedAccessibilityClassName = expandedAccessibilityClassName;
             other.disabledByPolicy = disabledByPolicy;
@@ -195,6 +198,7 @@
             sb.append(",label=").append(label);
             sb.append(",secondaryLabel=").append(secondaryLabel);
             sb.append(",contentDescription=").append(contentDescription);
+            sb.append(",stateDescription=").append(stateDescription);
             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
             sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
             sb.append(",disabledByPolicy=").append(disabledByPolicy);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1f13f8d..8d935ec 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2549,4 +2549,7 @@
 
     <!-- Quick Controls strings [CHAR LIMIT=30] -->
     <string name="quick_controls_title">Quick Controls</string>
+
+    <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
+    <string name="tile_unavailable">Unvailable</string>
 </resources>
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..557c64b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -191,6 +191,7 @@
         mTile.setLabel(tile.getLabel());
         mTile.setSubtitle(tile.getSubtitle());
         mTile.setContentDescription(tile.getContentDescription());
+        mTile.setStateDescription(tile.getStateDescription());
         mTile.setState(tile.getState());
     }
 
@@ -345,6 +346,12 @@
             state.contentDescription = state.label;
         }
 
+        if (mTile.getStateDescription() != null) {
+            state.stateDescription = mTile.getStateDescription();
+        } else {
+            state.stateDescription = null;
+        }
+
         if (state instanceof BooleanState) {
             state.expandedAccessibilityClassName = Switch.class.getName();
             ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 8b7f280..fda9e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -65,7 +65,6 @@
     private String mAccessibilityClass;
     private boolean mTileState;
     private boolean mCollapsedView;
-    private boolean mClicked;
     private boolean mShowRippleEffect = true;
 
     private final ImageView mBg;
@@ -234,13 +233,35 @@
         setLongClickable(state.handlesLongClick);
         mIcon.setIcon(state, allowAnimations);
         setContentDescription(state.contentDescription);
+        final StringBuilder stateDescription = new StringBuilder();
+        switch (state.state) {
+            case Tile.STATE_UNAVAILABLE:
+                stateDescription.append(mContext.getString(R.string.tile_unavailable));
+                break;
+            case Tile.STATE_INACTIVE:
+                if (state instanceof QSTile.BooleanState) {
+                    stateDescription.append(mContext.getString(R.string.switch_bar_off));
+                }
+                break;
+            case Tile.STATE_ACTIVE:
+                if (state instanceof QSTile.BooleanState) {
+                    stateDescription.append(mContext.getString(R.string.switch_bar_on));
+                }
+                break;
+            default:
+                break;
+        }
+        if (!TextUtils.isEmpty(state.stateDescription)) {
+            stateDescription.append(", ");
+            stateDescription.append(state.stateDescription);
+        }
+        setStateDescription(stateDescription.toString());
 
         mAccessibilityClass =
                 state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName;
         if (state instanceof QSTile.BooleanState) {
             boolean newState = ((BooleanState) state).value;
             if (mTileState != newState) {
-                mClicked = false;
                 mTileState = newState;
             }
         }
@@ -297,23 +318,10 @@
     }
 
     @Override
-    public boolean performClick() {
-        mClicked = true;
-        return super.performClick();
-    }
-
-    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         if (!TextUtils.isEmpty(mAccessibilityClass)) {
             event.setClassName(mAccessibilityClass);
-            if (Switch.class.getName().equals(mAccessibilityClass)) {
-                boolean b = mClicked ? !mTileState : mTileState;
-                String label = getResources()
-                        .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
-                event.setContentDescription(label);
-                event.setChecked(b);
-            }
         }
     }
 
@@ -325,11 +333,6 @@
         if (!TextUtils.isEmpty(mAccessibilityClass)) {
             info.setClassName(mAccessibilityClass);
             if (Switch.class.getName().equals(mAccessibilityClass)) {
-                boolean b = mClicked ? !mTileState : mTileState;
-                String label = getResources()
-                        .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
-                info.setText(label);
-                info.setChecked(b);
                 info.setCheckable(true);
                 if (isLongClickable()) {
                     info.addAction(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9282a2e..361b6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,25 +134,27 @@
         state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
         state.secondaryLabel = TextUtils.emptyIfNull(
                 getSecondaryLabel(enabled, connecting, connected, state.isTransient));
+        state.contentDescription = state.label;
+        state.stateDescription = "";
         if (enabled) {
             if (connected) {
                 state.icon = new BluetoothConnectedTileIcon();
                 if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
                     state.label = mController.getConnectedDeviceName();
                 }
-                state.contentDescription =
+                state.stateDescription =
                         mContext.getString(R.string.accessibility_bluetooth_name, state.label)
                                 + ", " + state.secondaryLabel;
             } else if (state.isTransient) {
                 state.icon = ResourceIcon.get(
                         com.android.internal.R.drawable.ic_bluetooth_transient_animation);
-                state.contentDescription = state.secondaryLabel;
+                state.stateDescription = state.secondaryLabel;
             } else {
                 state.icon =
                         ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth);
                 state.contentDescription = mContext.getString(
-                        R.string.accessibility_quick_settings_bluetooth) + ","
-                        + mContext.getString(R.string.accessibility_not_connected);
+                        R.string.accessibility_quick_settings_bluetooth);
+                state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
             }
             state.state = Tile.STATE_ACTIVE;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 0e813d1..58de057 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -183,6 +183,7 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.label = mContext.getString(R.string.quick_settings_cast_title);
         state.contentDescription = state.label;
+        state.stateDescription = "";
         state.value = false;
         final List<CastDevice> devices = mController.getCastDevices();
         boolean connecting = false;
@@ -192,8 +193,9 @@
             if (device.state == CastDevice.STATE_CONNECTED) {
                 state.value = true;
                 state.secondaryLabel = getDeviceName(device);
-                state.contentDescription = state.contentDescription + "," +
-                        mContext.getString(R.string.accessibility_cast_name, state.label);
+                state.stateDescription = state.stateDescription + ","
+                        + mContext.getString(
+                                R.string.accessibility_cast_name, state.label);
                 connecting = false;
                 break;
             } else if (device.state == CastDevice.STATE_CONNECTING) {
@@ -217,9 +219,8 @@
             state.state = Tile.STATE_UNAVAILABLE;
             String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
             state.secondaryLabel = noWifi;
-            state.contentDescription = state.contentDescription + ", " + mContext.getString(
-                    R.string.accessibility_quick_settings_not_available, noWifi);
         }
+        state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
         mDetailAdapter.updateItems(devices);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 22470c7..d5f86c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -194,17 +194,13 @@
             state.secondaryLabel = r.getString(R.string.cell_data_off);
         }
 
-
-        // TODO(b/77881974): Instead of switching out the description via a string check for
-        // we need to have two strings provided by the MobileIconGroup.
-        final CharSequence contentDescriptionSuffix;
+        state.contentDescription = state.label;
         if (state.state == Tile.STATE_INACTIVE) {
-            contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description);
+            // This information is appended later by converting the Tile.STATE_INACTIVE state.
+            state.stateDescription = "";
         } else {
-            contentDescriptionSuffix = state.secondaryLabel;
+            state.stateDescription = state.secondaryLabel;
         }
-
-        state.contentDescription = state.label + ", " + contentDescriptionSuffix;
     }
 
     private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 52d1a5b3..9215da4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -240,6 +240,8 @@
                 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
         state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd);
         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
+        // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
+        // to understand.
         switch (zen) {
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
                 state.contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index dafdd89..792c364 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -102,14 +102,13 @@
         }
         state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
         state.secondaryLabel = "";
+        state.stateDescription = "";
         if (!mFlashlightController.isAvailable()) {
             state.icon = mIcon;
             state.slash.isSlashed = true;
             state.secondaryLabel = mContext.getString(
                     R.string.quick_settings_flashlight_camera_in_use);
-            state.contentDescription = mContext.getString(
-                    R.string.accessibility_quick_settings_flashlight_unavailable)
-                    + ", " + state.secondaryLabel;
+            state.stateDescription = state.secondaryLabel;
             state.state = Tile.STATE_UNAVAILABLE;
             return;
         }
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..fd6b936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -147,6 +147,7 @@
 
         state.secondaryLabel = getSecondaryLabel(
                 isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices);
+        state.stateDescription = state.secondaryLabel;
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index fbdca3b..e617867 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -105,15 +105,8 @@
         }
         state.icon = mIcon;
         state.slash.isSlashed = !state.value;
-        if (locationEnabled) {
-            state.label = mContext.getString(R.string.quick_settings_location_label);
-            state.contentDescription = mContext.getString(
-                    R.string.accessibility_quick_settings_location_on);
-        } else {
-            state.label = mContext.getString(R.string.quick_settings_location_label);
-            state.contentDescription = mContext.getString(
-                    R.string.accessibility_quick_settings_location_off);
-        }
+        state.label = mContext.getString(R.string.quick_settings_location_label);
+        state.contentDescription = state.label;
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index b7ce101..6e8dcf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -195,6 +195,7 @@
         state.activityIn = cb.enabled && cb.activityIn;
         state.activityOut = cb.enabled && cb.activityOut;
         final StringBuffer minimalContentDescription = new StringBuffer();
+        final StringBuffer minimalStateDescription = new StringBuffer();
         final Resources r = mContext.getResources();
         if (isTransient) {
             state.icon = ResourceIcon.get(
@@ -219,13 +220,14 @@
                 mContext.getString(R.string.quick_settings_wifi_label)).append(",");
         if (state.value) {
             if (wifiConnected) {
-                minimalContentDescription.append(cb.wifiSignalContentDescription).append(",");
+                minimalStateDescription.append(cb.wifiSignalContentDescription);
                 minimalContentDescription.append(removeDoubleQuotes(cb.ssid));
                 if (!TextUtils.isEmpty(state.secondaryLabel)) {
                     minimalContentDescription.append(",").append(state.secondaryLabel);
                 }
             }
         }
+        state.stateDescription = minimalStateDescription.toString();
         state.contentDescription = minimalContentDescription.toString();
         state.dualLabelContentDescription = r.getString(
                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7853dc3..e54ee51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -103,14 +103,11 @@
         state.icon = mIcon;
         if (state.value) {
             state.slash.isSlashed = false;
-            state.contentDescription =  mContext.getString(
-                    R.string.accessibility_quick_settings_work_mode_on);
         } else {
             state.slash.isSlashed = true;
-            state.contentDescription =  mContext.getString(
-                    R.string.accessibility_quick_settings_work_mode_off);
         }
         state.label = mContext.getString(R.string.quick_settings_work_mode_label);
+        state.contentDescription = state.label;
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
     }
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java b/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
index 7c413779..9fda125 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
@@ -28,4 +28,9 @@
     public int getInterfaceVersion() {
         return IDhcpServerCallbacks.VERSION;
     }
+
+    @Override
+    public String getInterfaceHash() {
+        return IDhcpServerCallbacks.HASH;
+    }
 }
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 190d2509..f39e7af 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -301,6 +301,11 @@
         public int getInterfaceVersion() {
             return this.VERSION;
         }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
     }
 
     private class DhcpServerCallbacksImpl extends DhcpServerCallbacks {
diff --git a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
index 3218c0b..b1ffdb0 100644
--- a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
+++ b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
@@ -67,4 +67,9 @@
     public int getInterfaceVersion() {
         return INetdUnsolicitedEventListener.VERSION;
     }
+
+    @Override
+    public String getInterfaceHash() {
+        return INetdUnsolicitedEventListener.HASH;
+    }
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 0b7029b..5602d1a8 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -92,6 +92,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -670,7 +671,7 @@
 
                 service.onDataSharedLocked(request,
                         new DataShareCallbackDelegate(request, clientCancellationSignal,
-                                clientAdapter));
+                                clientAdapter, ContentCaptureManagerService.this));
             }
         }
 
@@ -918,20 +919,22 @@
         }
     }
 
-    // TODO(b/148265162): DataShareCallbackDelegate should be a static class keeping week references
-    //  to the needed info
-    private class DataShareCallbackDelegate extends IDataShareCallback.Stub {
+    private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
 
         @NonNull private final DataShareRequest mDataShareRequest;
-        @NonNull private final ICancellationSignal mClientCancellationSignal;
-        @NonNull private final IDataShareWriteAdapter mClientAdapter;
+        @NonNull
+        private final WeakReference<ICancellationSignal> mClientCancellationSignalReference;
+        @NonNull private final WeakReference<IDataShareWriteAdapter> mClientAdapterReference;
+        @NonNull private final WeakReference<ContentCaptureManagerService> mParentServiceReference;
 
         DataShareCallbackDelegate(@NonNull DataShareRequest dataShareRequest,
                 @NonNull ICancellationSignal clientCancellationSignal,
-                @NonNull IDataShareWriteAdapter clientAdapter) {
+                @NonNull IDataShareWriteAdapter clientAdapter,
+                ContentCaptureManagerService parentService) {
             mDataShareRequest = dataShareRequest;
-            mClientCancellationSignal = clientCancellationSignal;
-            mClientAdapter = clientAdapter;
+            mClientCancellationSignalReference = new WeakReference<>(clientCancellationSignal);
+            mClientAdapterReference = new WeakReference<>(clientAdapter);
+            mParentServiceReference = new WeakReference<>(parentService);
         }
 
         @Override
@@ -939,41 +942,52 @@
                 IDataShareReadAdapter serviceAdapter) throws RemoteException {
             Slog.i(TAG, "Data share request accepted by Content Capture service");
 
+            final ContentCaptureManagerService parentService = mParentServiceReference.get();
+            final ICancellationSignal clientCancellationSignal =
+                    mClientCancellationSignalReference.get();
+            final IDataShareWriteAdapter clientAdapter = mClientAdapterReference.get();
+            if (parentService == null || clientCancellationSignal == null
+                    || clientAdapter == null) {
+                Slog.w(TAG, "Can't fulfill accept() request, because remote objects have been "
+                        + "GC'ed");
+                return;
+            }
+
             Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
             if (clientPipe == null) {
-                mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+                clientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
                 serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
                 return;
             }
 
-            ParcelFileDescriptor source_in = clientPipe.second;
-            ParcelFileDescriptor sink_in = clientPipe.first;
+            ParcelFileDescriptor sourceIn = clientPipe.second;
+            ParcelFileDescriptor sinkIn = clientPipe.first;
 
             Pair<ParcelFileDescriptor, ParcelFileDescriptor> servicePipe = createPipe();
             if (servicePipe == null) {
-                bestEffortCloseFileDescriptors(source_in, sink_in);
+                bestEffortCloseFileDescriptors(sourceIn, sinkIn);
 
-                mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+                clientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
                 serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
                 return;
             }
 
-            ParcelFileDescriptor source_out = servicePipe.second;
-            ParcelFileDescriptor sink_out = servicePipe.first;
+            ParcelFileDescriptor sourceOut = servicePipe.second;
+            ParcelFileDescriptor sinkOut = servicePipe.first;
 
             ICancellationSignal cancellationSignalTransport =
                     CancellationSignal.createTransport();
-            mPackagesWithShareRequests.add(mDataShareRequest.getPackageName());
+            parentService.mPackagesWithShareRequests.add(mDataShareRequest.getPackageName());
 
-            mClientAdapter.write(source_in);
-            serviceAdapter.start(sink_out, cancellationSignalTransport);
+            clientAdapter.write(sourceIn);
+            serviceAdapter.start(sinkOut, cancellationSignalTransport);
 
             CancellationSignal cancellationSignal =
                     CancellationSignal.fromTransport(cancellationSignalTransport);
 
             cancellationSignal.setOnCancelListener(() -> {
                 try {
-                    mClientCancellationSignal.cancel();
+                    clientCancellationSignal.cancel();
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to propagate cancel operation to the caller", e);
                 }
@@ -982,13 +996,13 @@
             // File descriptor received by the client app will be a copy of the current one. Close
             // the one that belongs to the system server, so there's only 1 open left for the
             // current pipe.
-            bestEffortCloseFileDescriptor(source_in);
+            bestEffortCloseFileDescriptor(sourceIn);
 
-            mDataShareExecutor.execute(() -> {
+            parentService.mDataShareExecutor.execute(() -> {
                 try (InputStream fis =
-                             new ParcelFileDescriptor.AutoCloseInputStream(sink_in);
+                             new ParcelFileDescriptor.AutoCloseInputStream(sinkIn);
                      OutputStream fos =
-                             new ParcelFileDescriptor.AutoCloseOutputStream(source_out)) {
+                             new ParcelFileDescriptor.AutoCloseOutputStream(sourceOut)) {
 
                     byte[] byteBuffer = new byte[DATA_SHARE_BYTE_BUFFER_LENGTH];
                     while (true) {
@@ -1005,52 +1019,86 @@
                 }
             });
 
-            mHandler.postDelayed(() -> {
-                synchronized (mLock) {
-                    mPackagesWithShareRequests.remove(mDataShareRequest.getPackageName());
-
-                    // Interaction finished successfully <=> all data has been written to Content
-                    // Capture Service. If it hasn't been read successfully, service would be able
-                    // to signal through the cancellation signal.
-                    boolean finishedSuccessfully = !sink_in.getFileDescriptor().valid()
-                            && !source_out.getFileDescriptor().valid();
-
-                    if (finishedSuccessfully) {
-                        Slog.i(TAG, "Content capture data sharing session terminated "
-                                + "successfully for package '"
-                                + mDataShareRequest.getPackageName()
-                                + "'");
-                    } else {
-                        Slog.i(TAG, "Reached the timeout of Content Capture data sharing session "
-                                + "for package '"
-                                + mDataShareRequest.getPackageName()
-                                + "', terminating the pipe.");
-                    }
-
-                    // Ensure all the descriptors are closed after the session.
-                    bestEffortCloseFileDescriptors(source_in, sink_in, source_out, sink_out);
-
-                    if (!finishedSuccessfully) {
-                        try {
-                            mClientCancellationSignal.cancel();
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Failed to cancel() the client operation", e);
-                        }
-                        try {
-                            serviceCancellationSignal.cancel();
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Failed to cancel() the service operation", e);
-                        }
-                    }
-                }
-            }, MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS);
+            parentService.mHandler.postDelayed(() ->
+                    enforceDataSharingTtl(
+                            sourceIn,
+                            sinkIn,
+                            sourceOut,
+                            sinkOut,
+                            new WeakReference<>(serviceCancellationSignal)),
+                    MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS);
         }
 
         @Override
         public void reject() throws RemoteException {
             Slog.i(TAG, "Data share request rejected by Content Capture service");
 
-            mClientAdapter.rejected();
+            IDataShareWriteAdapter clientAdapter = mClientAdapterReference.get();
+            if (clientAdapter == null) {
+                Slog.w(TAG, "Can't fulfill reject() request, because remote objects have been "
+                        + "GC'ed");
+                return;
+            }
+
+            clientAdapter.rejected();
+        }
+
+        private void enforceDataSharingTtl(ParcelFileDescriptor sourceIn,
+                ParcelFileDescriptor sinkIn,
+                ParcelFileDescriptor sourceOut,
+                ParcelFileDescriptor sinkOut,
+                WeakReference<ICancellationSignal> serviceCancellationSignalReference) {
+
+            final ContentCaptureManagerService parentService = mParentServiceReference.get();
+            final ICancellationSignal clientCancellationSignal =
+                    mClientCancellationSignalReference.get();
+            final ICancellationSignal serviceCancellationSignal =
+                    serviceCancellationSignalReference.get();
+            if (parentService == null || clientCancellationSignal == null
+                    || serviceCancellationSignal == null) {
+                Slog.w(TAG, "Can't enforce data sharing TTL, because remote objects have been "
+                        + "GC'ed");
+                return;
+            }
+
+            synchronized (parentService.mLock) {
+                parentService.mPackagesWithShareRequests
+                        .remove(mDataShareRequest.getPackageName());
+
+                // Interaction finished successfully <=> all data has been written to Content
+                // Capture Service. If it hasn't been read successfully, service would be able
+                // to signal through the cancellation signal.
+                boolean finishedSuccessfully = !sinkIn.getFileDescriptor().valid()
+                        && !sourceOut.getFileDescriptor().valid();
+
+                if (finishedSuccessfully) {
+                    Slog.i(TAG, "Content capture data sharing session terminated "
+                            + "successfully for package '"
+                            + mDataShareRequest.getPackageName()
+                            + "'");
+                } else {
+                    Slog.i(TAG, "Reached the timeout of Content Capture data sharing session "
+                            + "for package '"
+                            + mDataShareRequest.getPackageName()
+                            + "', terminating the pipe.");
+                }
+
+                // Ensure all the descriptors are closed after the session.
+                bestEffortCloseFileDescriptors(sourceIn, sinkIn, sourceOut, sinkOut);
+
+                if (!finishedSuccessfully) {
+                    try {
+                        clientCancellationSignal.cancel();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to cancel() the client operation", e);
+                    }
+                    try {
+                        serviceCancellationSignal.cancel();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to cancel() the service operation", e);
+                    }
+                }
+            }
         }
 
         private Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index dd33566..d933e9d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2962,6 +2962,11 @@
         public int getInterfaceVersion() {
             return this.VERSION;
         }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
     }
 
     private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 05d8360..9d4c783 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -735,6 +735,11 @@
         public int getInterfaceVersion() {
             return INetdUnsolicitedEventListener.VERSION;
         }
+
+        @Override
+        public String getInterfaceHash() {
+            return INetdUnsolicitedEventListener.HASH;
+        }
     }
 
     //
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f2892cc..17e2f69 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -310,6 +310,11 @@
         return this.VERSION;
     }
 
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
     private void addWakeupEvent(WakeupEvent event) {
         String iface = event.iface;
         mWakeupEvents.append(event);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 26812f4..9e3292b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1595,11 +1595,11 @@
                 mRootWindowContainer.resumeFocusedStacksTopActivities(
                         mTargetStack, mStartActivity, mOptions);
             }
-        } else if (mStartActivity != null) {
-            mSupervisor.mRecentTasks.add(mStartActivity.getTask());
         }
         mRootWindowContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);
 
+        // Update the recent tasks list immediately when the activity starts
+        mSupervisor.mRecentTasks.add(mStartActivity.getTask());
         mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(),
                 preferredWindowingMode, mPreferredDisplayId, mTargetStack);
 
diff --git a/services/net/java/android/net/IpMemoryStore.java b/services/net/java/android/net/IpMemoryStore.java
index 6f91e00..dcefb53 100644
--- a/services/net/java/android/net/IpMemoryStore.java
+++ b/services/net/java/android/net/IpMemoryStore.java
@@ -52,6 +52,11 @@
                     public int getInterfaceVersion() {
                         return this.VERSION;
                     }
+
+                    @Override
+                    public String getInterfaceHash() {
+                        return this.HASH;
+                    }
                 });
     }
 
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 7f723b1..a3618b4 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -189,6 +189,11 @@
         public int getInterfaceVersion() {
             return this.VERSION;
         }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 0fc2bc5..bab877e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1008,4 +1008,18 @@
                 .setOutActivity(outActivity).execute();
         assertThat(outActivity[0].inSplitScreenSecondaryWindowingMode()).isTrue();
     }
+
+    @Test
+    public void testActivityStart_expectAddedToRecentTask() {
+        RecentTasks recentTasks = mock(RecentTasks.class);
+        mService.mStackSupervisor.setRecentTasks(recentTasks);
+        doReturn(true).when(recentTasks).isCallerRecents(anyInt());
+
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+
+        starter.setReason("testAddToTaskListOnActivityStart")
+                .execute();
+
+        verify(recentTasks, times(1)).add(any());
+    }
 }