Merge "Configurable SoftAP: Add System API."
diff --git a/Android.bp b/Android.bp
index b26b373..9497085 100644
--- a/Android.bp
+++ b/Android.bp
@@ -307,6 +307,7 @@
         "android.hardware.thermal-V1.1-java",
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
+        "android.hardware.tv.tuner-V1.0-java-constants",
         "android.hardware.usb-V1.0-java-constants",
         "android.hardware.usb-V1.1-java-constants",
         "android.hardware.usb-V1.2-java-constants",
@@ -935,6 +936,7 @@
 ]
 
 metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
+    "--ignore-classes-on-classpath " +
     "--hide-package com.android.okhttp " +
     "--hide-package com.android.org.conscrypt --hide-package com.android.server " +
     "--error UnhiddenSystemApi " +
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 1a10753..5e5a04b 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -156,8 +156,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -2071,9 +2069,7 @@
 
             CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
             HistoricalOpsRequest histOpsRequest =
-                    new HistoricalOpsRequest.Builder(
-                            Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli(),
-                            Long.MAX_VALUE).build();
+                    new HistoricalOpsRequest.Builder(0, Long.MAX_VALUE).build();
             appOps.getHistoricalOps(histOpsRequest, mContext.getMainExecutor(), ops::complete);
 
             HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
diff --git a/api/current.txt b/api/current.txt
index ea27baf..e31aed0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9467,6 +9467,7 @@
     method @NonNull public final android.content.ContentProvider.CallingIdentity clearCallingIdentity();
     method public abstract int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]);
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
+    method @Nullable public final String getCallingFeatureId();
     method @Nullable public final String getCallingPackage();
     method @Nullable public final android.content.Context getContext();
     method @Nullable public final android.content.pm.PathPermission[] getPathPermissions();
@@ -38919,6 +38920,7 @@
     field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
     field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
+    field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
     field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
     field @Deprecated public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS";
     field public static final String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -44379,6 +44381,7 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+    field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
     field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
     field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
     field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
diff --git a/api/system-current.txt b/api/system-current.txt
index a3001d5..a35148a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3639,7 +3639,7 @@
   public final class MediaRecorder.AudioSource {
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf
-    field public static final int RADIO_TUNER = 1998; // 0x7ce
+    field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
   }
 
   public class PlayerProxy {
@@ -4764,6 +4764,7 @@
     method @Deprecated public boolean isEphemeral();
     method @Deprecated public boolean isNoInternetAccessExpected();
     field @Deprecated public boolean allowAutojoin;
+    field @Deprecated public int carrierId;
     field @Deprecated public String creatorName;
     field @Deprecated public int creatorUid;
     field @Deprecated public String lastUpdateName;
@@ -4871,6 +4872,10 @@
     field public int numUsage;
   }
 
+  public static final class WifiNetworkSuggestion.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+  }
+
   public class WifiScanner {
     method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
     method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
@@ -6265,15 +6270,21 @@
     field public static final String DELIVERY_TIME = "date";
     field public static final String ETWS_WARNING_TYPE = "etws_warning_type";
     field public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+    field public static final String GEOMETRIES = "geometries";
     field public static final String LAC = "lac";
     field public static final String LANGUAGE_CODE = "language";
+    field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
     field public static final String MESSAGE_BODY = "body";
+    field public static final String MESSAGE_BROADCASTED = "message_broadcasted";
     field public static final String MESSAGE_FORMAT = "format";
+    field @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) @NonNull public static final android.net.Uri MESSAGE_HISTORY_URI;
     field public static final String MESSAGE_PRIORITY = "priority";
     field public static final String MESSAGE_READ = "read";
     field public static final String PLMN = "plmn";
+    field public static final String RECEIVED_TIME = "received_time";
     field public static final String SERIAL_NUMBER = "serial_number";
     field public static final String SERVICE_CATEGORY = "service_category";
+    field public static final String SLOT_INDEX = "slot_index";
   }
 
   public final class TimeZoneRulesDataContract {
@@ -8362,6 +8373,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableModemForSlot(int, boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean);
+    method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void factoryReset(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
@@ -9031,11 +9043,14 @@
 
   public class ImsMmTelManager {
     method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull java.util.concurrent.Executor) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void isSupported(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, @NonNull java.util.function.Consumer<java.lang.Boolean>, @NonNull java.util.concurrent.Executor) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isTtyOverVolteEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
@@ -9562,6 +9577,8 @@
     method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
     method public void setUiTtyMode(int, @Nullable android.os.Message);
     method @android.telephony.ims.feature.MmTelFeature.ProcessCallResult public int shouldProcessCall(@NonNull String[]);
+    field public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
+    field public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";
     field public static final int PROCESS_CALL_CSFB = 1; // 0x1
     field public static final int PROCESS_CALL_IMS = 0; // 0x0
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index a060dfc..1c18ccd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2456,6 +2456,36 @@
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
   }
 
+  public static final class Telephony.CellBroadcasts implements android.provider.BaseColumns {
+    field public static final String CID = "cid";
+    field public static final String CMAS_CATEGORY = "cmas_category";
+    field public static final String CMAS_CERTAINTY = "cmas_certainty";
+    field public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+    field public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+    field public static final String CMAS_SEVERITY = "cmas_severity";
+    field public static final String CMAS_URGENCY = "cmas_urgency";
+    field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DEFAULT_SORT_ORDER = "date DESC";
+    field public static final String DELIVERY_TIME = "date";
+    field public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+    field public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+    field public static final String GEOMETRIES = "geometries";
+    field public static final String LAC = "lac";
+    field public static final String LANGUAGE_CODE = "language";
+    field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
+    field public static final String MESSAGE_BODY = "body";
+    field public static final String MESSAGE_BROADCASTED = "message_broadcasted";
+    field public static final String MESSAGE_FORMAT = "format";
+    field @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) @NonNull public static final android.net.Uri MESSAGE_HISTORY_URI;
+    field public static final String MESSAGE_PRIORITY = "priority";
+    field public static final String MESSAGE_READ = "read";
+    field public static final String PLMN = "plmn";
+    field public static final String RECEIVED_TIME = "received_time";
+    field public static final String SERIAL_NUMBER = "serial_number";
+    field public static final String SERVICE_CATEGORY = "service_category";
+    field public static final String SLOT_INDEX = "slot_index";
+  }
+
   public static final class Telephony.Sms.Intents {
     field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
   }
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 55dbc17..7e278e9 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -508,7 +508,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.insert(resolveCallingPackage(), mUri, mContentValues);
+            provider.insert(resolveCallingPackage(), null, mUri, mContentValues);
         }
     }
 
@@ -522,7 +522,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.delete(resolveCallingPackage(), mUri, mWhere, null);
+            provider.delete(resolveCallingPackage(), null, mUri, mWhere, null);
         }
     }
 
@@ -557,7 +557,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            Bundle result = provider.call(null, mUri.getAuthority(), mMethod, mArg, mExtras);
+            Bundle result = provider.call(null, null, mUri.getAuthority(), mMethod, mArg, mExtras);
             if (result != null) {
                 result.size(); // unpack
             }
@@ -584,7 +584,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) {
+            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "r", null, null)) {
                 FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out);
             }
         }
@@ -597,7 +597,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) {
+            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "w", null, null)) {
                 FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor());
             }
         }
@@ -616,7 +616,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection,
+            Cursor cursor = provider.query(resolveCallingPackage(), null, mUri, mProjection,
                     ContentResolver.createSqlQueryBundle(mWhere, null, mSortOrder), null);
             if (cursor == null) {
                 System.out.println("No result found.");
@@ -679,7 +679,7 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            provider.update(resolveCallingPackage(), mUri, mContentValues, mWhere, null);
+            provider.update(resolveCallingPackage(), null, mUri, mContentValues, mWhere, null);
         }
     }
 
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 6c3dff2..91cadc9 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -200,6 +200,10 @@
 }
 
 void StatsLogProcessor::OnLogEvent(LogEvent* event) {
+    OnLogEvent(event, getElapsedRealtimeNs());
+}
+
+void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
 #ifdef VERY_VERBOSE_PRINTING
@@ -207,9 +211,9 @@
         ALOGI("%s", event->ToString().c_str());
     }
 #endif
-    const int64_t currentTimestampNs = event->GetElapsedTimestampNs();
+    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
 
-    resetIfConfigTtlExpiredLocked(currentTimestampNs);
+    resetIfConfigTtlExpiredLocked(eventElapsedTimeNs);
 
     StatsdStats::getInstance().noteAtomLogged(
         event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
@@ -264,15 +268,16 @@
             uidsWithActiveConfigsChanged.insert(uid);
             StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive);
         }
-        flushIfNecessaryLocked(event->GetElapsedTimestampNs(), pair.first, *(pair.second));
+        flushIfNecessaryLocked(pair.first, *(pair.second));
     }
 
+    // Don't use the event timestamp for the guardrail.
     for (int uid : uidsWithActiveConfigsChanged) {
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid);
         if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) {
-            if (currentTimestampNs - lastBroadcastTime->second <
-                    StatsdStats::kMinActivationBroadcastPeriodNs) {
+            if (elapsedRealtimeNs - lastBroadcastTime->second <
+                StatsdStats::kMinActivationBroadcastPeriodNs) {
                 StatsdStats::getInstance().noteActivationBroadcastGuardrailHit(uid);
                 VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us.");
                 return;
@@ -282,13 +287,13 @@
         if (activeConfigs != activeConfigsPerUid.end()) {
             if (mSendActivationBroadcast(uid, activeConfigs->second)) {
                 VLOG("StatsD sent activation notice for uid %d", uid);
-                mLastActivationBroadcastTimes[uid] = currentTimestampNs;
+                mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs;
             }
         } else {
             std::vector<int64_t> emptyActiveConfigs;
             if (mSendActivationBroadcast(uid, emptyActiveConfigs)) {
                 VLOG("StatsD sent EMPTY activation notice for uid %d", uid);
-                mLastActivationBroadcastTimes[uid] = currentTimestampNs;
+                mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs;
             }
         }
     }
@@ -550,22 +555,23 @@
     }
 }
 
-void StatsLogProcessor::flushIfNecessaryLocked(
-    int64_t timestampNs, const ConfigKey& key, MetricsManager& metricsManager) {
+void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key,
+                                               MetricsManager& metricsManager) {
+    int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
     auto lastCheckTime = mLastByteSizeTimes.find(key);
     if (lastCheckTime != mLastByteSizeTimes.end()) {
-        if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
+        if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
             return;
         }
     }
 
     // We suspect that the byteSize() computation is expensive, so we set a rate limit.
     size_t totalBytes = metricsManager.byteSize();
-    mLastByteSizeTimes[key] = timestampNs;
+    mLastByteSizeTimes[key] = elapsedRealtimeNs;
     bool requestDump = false;
-    if (totalBytes >
-        StatsdStats::kMaxMetricsBytesPerConfig) {  // Too late. We need to start clearing data.
-        metricsManager.dropData(timestampNs);
+    if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) {
+        // Too late. We need to start clearing data.
+        metricsManager.dropData(elapsedRealtimeNs);
         StatsdStats::getInstance().noteDataDropped(key, totalBytes);
         VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
     } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
@@ -580,7 +586,8 @@
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastBroadcastTimes.find(key);
         if (lastBroadcastTime != mLastBroadcastTimes.end()) {
-            if (timestampNs - lastBroadcastTime->second < StatsdStats::kMinBroadcastPeriodNs) {
+            if (elapsedRealtimeNs - lastBroadcastTime->second <
+                    StatsdStats::kMinBroadcastPeriodNs) {
                 VLOG("StatsD would've sent a broadcast but the rate limit stopped us.");
                 return;
             }
@@ -588,7 +595,7 @@
         if (mSendBroadcast(key)) {
             mOnDiskDataConfigs.erase(key);
             VLOG("StatsD triggered data fetch for %s", key.ToString().c_str());
-            mLastBroadcastTimes[key] = timestampNs;
+            mLastBroadcastTimes[key] = elapsedRealtimeNs;
             StatsdStats::getInstance().noteBroadcastSent(key);
         }
     }
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 8292a3a..68b1218 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -147,6 +147,8 @@
 
     sp<AlarmMonitor> mPeriodicAlarmMonitor;
 
+    void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs);
+
     void resetIfConfigTtlExpiredLocked(const int64_t timestampNs);
 
     void OnConfigUpdatedLocked(
@@ -176,8 +178,7 @@
 
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
-    void flushIfNecessaryLocked(int64_t timestampNs, const ConfigKey& key,
-                                MetricsManager& metricsManager);
+    void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager);
 
     // Maps the isolated uid in the log event to host uid if the log event contains uid fields.
     void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const;
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 460b9e0..69e11ed 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -76,9 +76,9 @@
     // Expect only the first flush to trigger a check for byte size since the last two are
     // rate-limited.
     EXPECT_CALL(mockMetricsManager, byteSize()).Times(1);
-    p.flushIfNecessaryLocked(99, key, mockMetricsManager);
-    p.flushIfNecessaryLocked(100, key, mockMetricsManager);
-    p.flushIfNecessaryLocked(101, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
 }
 
 TEST(StatsLogProcessorTest, TestRateLimitBroadcast) {
@@ -103,7 +103,7 @@
                     StatsdStats::kMaxMetricsBytesPerConfig * .95)));
 
     // Expect only one broadcast despite always returning a size that should trigger broadcast.
-    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
     EXPECT_EQ(1, broadcastCount);
 
     // b/73089712
@@ -136,7 +136,7 @@
     EXPECT_CALL(mockMetricsManager, dropData(_)).Times(1);
 
     // Expect to call the onDumpReport and skip the broadcast.
-    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(key, mockMetricsManager);
     EXPECT_EQ(0, broadcastCount);
 }
 
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 5da0fca..9093155 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -271,19 +271,19 @@
     // Turn screen off.
     event = CreateScreenStateChangedEvent(
             android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * NS_PER_SEC); // 0:02
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 2 * NS_PER_SEC);
 
     // Turn screen on.
     const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), durationStartNs);
 
     // Activate metric.
     const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10
     const int64_t activationEndNs =
             activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40
     event = CreateAppCrashEvent(111, activationStartNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), activationStartNs);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -296,7 +296,7 @@
     // Expire activation.
     const int64_t expirationNs = activationEndNs + 7 * NS_PER_SEC;
     event = CreateScreenBrightnessChangedEvent(64, expirationNs); // 0:47
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), expirationNs);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 2);
@@ -310,24 +310,24 @@
     // Turn off screen 10 seconds after activation expiration.
     const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(),durationEndNs);
 
     // Turn screen on.
     const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, duration2StartNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), duration2StartNs);
 
     // Turn off screen.
     const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, duration2EndNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), duration2EndNs);
 
     // Activate metric.
     const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10
     const int64_t activation2EndNs =
             activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40
     event = CreateAppCrashEvent(211, activation2StartNs);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), activation2StartNs);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index f1b6029..b6a6492 100644
--- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -290,14 +290,14 @@
     std::unique_ptr<LogEvent> event;
 
     event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -312,12 +312,12 @@
 
     // First processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -330,7 +330,7 @@
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
     event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -344,11 +344,11 @@
 
     // 3rd processed event.
     event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
     event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -364,7 +364,7 @@
     // Re-activate metric via screen on.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -379,7 +379,7 @@
 
     // 4th processed event.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
@@ -509,14 +509,14 @@
     std::unique_ptr<LogEvent> event;
 
     event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -532,12 +532,12 @@
 
     // First processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -551,7 +551,7 @@
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
     event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(),bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -566,11 +566,11 @@
 
     // 3rd processed event.
     event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
     event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -587,7 +587,7 @@
     // Re-activate metric via screen on.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -603,11 +603,11 @@
 
     // 4th processed event.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -623,11 +623,11 @@
 
     // 5th processed event.
     event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode activation.
     event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -643,7 +643,7 @@
 
     // Screen-on activation expired.
     event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -658,11 +658,11 @@
     EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]);
 
     event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 5);
@@ -678,7 +678,7 @@
 
     // Cancel battery saver mode activation.
     event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 6);
@@ -835,14 +835,14 @@
     std::unique_ptr<LogEvent> event;
 
     event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 0);
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -859,12 +859,12 @@
 
     // First processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -879,7 +879,7 @@
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
     event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -895,11 +895,11 @@
 
     // 3rd processed event.
     event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
     event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -917,7 +917,7 @@
     // Re-activate metric via screen on.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -934,11 +934,11 @@
 
     // 4th processed event.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -955,11 +955,11 @@
 
     // 5th processed event.
     event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode and screen on activation.
     event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -976,7 +976,7 @@
 
     // Screen-on activation expired.
     event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 4);
@@ -991,11 +991,11 @@
     EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]);
 
     event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 5);
@@ -1012,7 +1012,7 @@
 
     // Cancel battery saver mode and screen on activation.
     event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 6);
@@ -1170,11 +1170,11 @@
 
     // Event that should be ignored.
     event = CreateAppCrashEvent(111, bucketStartTimeNs + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 1);
 
     // Activate metric via screen on for 2 minutes.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -1186,11 +1186,11 @@
 
     // 1st processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Enable battery saver mode activation for 5 minutes.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 1);
@@ -1201,12 +1201,12 @@
 
     // 2nd processed event.
     event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 + 40);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 40);
 
     // Cancel battery saver mode and screen on activation.
     int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61;
     event = CreateScreenBrightnessChangedEvent(64, firstDeactivation);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), firstDeactivation);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     // New broadcast since the config is no longer active.
@@ -1217,11 +1217,11 @@
 
     // Should be ignored
     event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 61 + 80);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 61 + 80);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 3);
@@ -1233,12 +1233,12 @@
 
     // 3rd processed event.
     event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80);
 
     // Cancel battery saver mode activation.
     int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13;
     event = CreateScreenBrightnessChangedEvent(140, secondDeactivation);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), secondDeactivation);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(broadcastCount, 4);
@@ -1248,7 +1248,7 @@
 
     // Should be ignored.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80);
 
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
@@ -1388,9 +1388,9 @@
     std::unique_ptr<LogEvent> event;
 
     event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     event = CreateMoveToForegroundEvent(1111, bucketStartTimeNs + 5);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 5);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_FALSE(metricProducer2->mIsActive);
@@ -1398,7 +1398,7 @@
 
     // Activated by battery save mode.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 1);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
@@ -1424,14 +1424,14 @@
 
     // First processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
     event = CreateMoveToForegroundEvent(2222, bucketStartTimeNs + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 15);
 
     // Activated by screen on event.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + 20);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + 20);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1455,9 +1455,9 @@
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
     event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     event = CreateMoveToForegroundEvent(3333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
@@ -1482,15 +1482,15 @@
 
     // 3rd processed event.
     event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
     event = CreateMoveToForegroundEvent(4444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
 
     // All activations expired.
     event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     event = CreateMoveToForegroundEvent(5555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8);
     EXPECT_FALSE(metricsManager->isActive());
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
@@ -1517,7 +1517,7 @@
     // Re-activate metric via screen on.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 3);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
@@ -1543,13 +1543,13 @@
 
     // 4th processed event.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
     event = CreateMoveToForegroundEvent(6666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 3);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
@@ -1575,13 +1575,13 @@
 
     // 5th processed event.
     event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
     event = CreateMoveToForegroundEvent(7777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
 
     // Cancel battery saver mode and screen on activation.
     event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(),bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
     EXPECT_FALSE(metricsManager->isActive());
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 4);
@@ -1607,9 +1607,9 @@
 
     // Screen-on activation expired.
     event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     event = CreateMoveToForegroundEvent(8888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 4);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
@@ -1633,13 +1633,13 @@
     EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]);
 
     event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
     event = CreateMoveToForegroundEvent(9999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
 
     // Re-enable battery saver mode activation.
     event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 5);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
@@ -1665,7 +1665,7 @@
 
     // Cancel battery saver mode and screen on activation.
     event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
-    processor.OnLogEvent(event.get());
+    processor.OnLogEvent(event.get(),bucketStartTimeNs + NS_PER_SEC * 60 * 16);
     EXPECT_FALSE(metricsManager->isActive());
     EXPECT_EQ(broadcastCount, 6);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
index 455e4bb..b23bf5d 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -67,7 +67,7 @@
                     throw new IllegalStateException("Could not find provider: " + providerName);
                 }
                 provider = holder.provider;
-                cursor = provider.query(null, Settings.Secure.CONTENT_URI,
+                cursor = provider.query(null, null, Settings.Secure.CONTENT_URI,
                         new String[] {
                             Settings.Secure.VALUE
                         },
diff --git a/core/java/android/app/NotificationHistory.aidl b/core/java/android/app/NotificationHistory.aidl
new file mode 100644
index 0000000..8150e74
--- /dev/null
+++ b/core/java/android/app/NotificationHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable NotificationHistory;
\ No newline at end of file
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
new file mode 100644
index 0000000..c35246b
--- /dev/null
+++ b/core/java/android/app/NotificationHistory.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class NotificationHistory implements Parcelable {
+
+    /**
+     * A historical notification. Any new fields added here should also be added to
+     * {@link #readNotificationFromParcel} and
+     * {@link #writeNotificationToParcel(HistoricalNotification, Parcel, int)}.
+     */
+    public static final class HistoricalNotification {
+        private String mPackage;
+        private String mChannelName;
+        private String mChannelId;
+        private int mUid;
+        private @UserIdInt int mUserId;
+        private long mPostedTimeMs;
+        private String mTitle;
+        private String mText;
+        private Icon mIcon;
+
+        private HistoricalNotification() {}
+
+        public String getPackage() {
+            return mPackage;
+        }
+
+        public String getChannelName() {
+            return mChannelName;
+        }
+
+        public String getChannelId() {
+            return mChannelId;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getUserId() {
+            return mUserId;
+        }
+
+        public long getPostedTimeMs() {
+            return mPostedTimeMs;
+        }
+
+        public String getTitle() {
+            return mTitle;
+        }
+
+        public String getText() {
+            return mText;
+        }
+
+        public Icon getIcon() {
+            return mIcon;
+        }
+
+        public String getKey() {
+            return mPackage + "|" + mUid + "|" + mPostedTimeMs;
+        }
+
+        @Override
+        public String toString() {
+            return "HistoricalNotification{" +
+                    "key='" + getKey() + '\'' +
+                    ", mChannelName='" + mChannelName + '\'' +
+                    ", mChannelId='" + mChannelId + '\'' +
+                    ", mUserId=" + mUserId +
+                    ", mTitle='" + mTitle + '\'' +
+                    ", mText='" + mText + '\'' +
+                    ", mIcon=" + mIcon +
+                    '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            HistoricalNotification that = (HistoricalNotification) o;
+            boolean iconsAreSame = getIcon() == null && that.getIcon() == null
+                    || (getIcon() != null && that.getIcon() != null
+                    && getIcon().sameAs(that.getIcon()));
+            return getUid() == that.getUid() &&
+                    getUserId() == that.getUserId() &&
+                    getPostedTimeMs() == that.getPostedTimeMs() &&
+                    Objects.equals(getPackage(), that.getPackage()) &&
+                    Objects.equals(getChannelName(), that.getChannelName()) &&
+                    Objects.equals(getChannelId(), that.getChannelId()) &&
+                    Objects.equals(getTitle(), that.getTitle()) &&
+                    Objects.equals(getText(), that.getText()) &&
+                    iconsAreSame;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getPackage(), getChannelName(), getChannelId(), getUid(),
+                    getUserId(),
+                    getPostedTimeMs(), getTitle(), getText(), getIcon());
+        }
+
+        public static final class Builder {
+            private String mPackage;
+            private String mChannelName;
+            private String mChannelId;
+            private int mUid;
+            private @UserIdInt int mUserId;
+            private long mPostedTimeMs;
+            private String mTitle;
+            private String mText;
+            private Icon mIcon;
+
+            public Builder() {}
+
+            public Builder setPackage(String aPackage) {
+                mPackage = aPackage;
+                return this;
+            }
+
+            public Builder setChannelName(String channelName) {
+                mChannelName = channelName;
+                return this;
+            }
+
+            public Builder setChannelId(String channelId) {
+                mChannelId = channelId;
+                return this;
+            }
+
+            public Builder setUid(int uid) {
+                mUid = uid;
+                return this;
+            }
+
+            public Builder setUserId(int userId) {
+                mUserId = userId;
+                return this;
+            }
+
+            public Builder setPostedTimeMs(long postedTimeMs) {
+                mPostedTimeMs = postedTimeMs;
+                return this;
+            }
+
+            public Builder setTitle(String title) {
+                mTitle = title;
+                return this;
+            }
+
+            public Builder setText(String text) {
+                mText = text;
+                return this;
+            }
+
+            public Builder setIcon(Icon icon) {
+                mIcon = icon;
+                return this;
+            }
+
+            public HistoricalNotification build() {
+                HistoricalNotification n = new HistoricalNotification();
+                n.mPackage = mPackage;
+                n.mChannelName = mChannelName;
+                n.mChannelId = mChannelId;
+                n.mUid = mUid;
+                n.mUserId = mUserId;
+                n.mPostedTimeMs = mPostedTimeMs;
+                n.mTitle = mTitle;
+                n.mText = mText;
+                n.mIcon = mIcon;
+                return n;
+            }
+        }
+    }
+
+    // Only used when creating the resulting history. Not used for reading/unparceling.
+    private List<HistoricalNotification> mNotificationsToWrite = new ArrayList<>();
+    // ditto
+    private Set<String> mStringsToWrite = new HashSet<>();
+
+    // Mostly used for reading/unparceling events.
+    private Parcel mParcel = null;
+    private int mHistoryCount;
+    private int mIndex = 0;
+
+    // Sorted array of commonly used strings to shrink the size of the parcel. populated from
+    // mStringsToWrite on write and the parcel on read.
+    private String[] mStringPool;
+
+    /**
+     * Construct the iterator from a parcel.
+     */
+    private NotificationHistory(Parcel in) {
+        byte[] bytes = in.readBlob();
+        Parcel data = Parcel.obtain();
+        data.unmarshall(bytes, 0, bytes.length);
+        data.setDataPosition(0);
+        mHistoryCount = data.readInt();
+        mIndex = data.readInt();
+        if (mHistoryCount > 0) {
+            mStringPool = data.createStringArray();
+
+            final int listByteLength = data.readInt();
+            final int positionInParcel = data.readInt();
+            mParcel = Parcel.obtain();
+            mParcel.setDataPosition(0);
+            mParcel.appendFrom(data, data.dataPosition(), listByteLength);
+            mParcel.setDataSize(mParcel.dataPosition());
+            mParcel.setDataPosition(positionInParcel);
+        }
+    }
+
+    /**
+     * Create an empty iterator.
+     */
+    public NotificationHistory() {
+        mHistoryCount = 0;
+    }
+
+    /**
+     * Returns whether or not there are more events to read using {@link #getNextNotification()}.
+     *
+     * @return true if there are more events, false otherwise.
+     */
+    public boolean hasNextNotification() {
+        return mIndex < mHistoryCount;
+    }
+
+    /**
+     * Retrieve the next {@link HistoricalNotification} from the collection and put the
+     * resulting data into {@code notificationOut}.
+     *
+     * @return The next {@link HistoricalNotification} or null if there are no more notifications.
+     */
+    public @Nullable HistoricalNotification getNextNotification() {
+        if (!hasNextNotification()) {
+            return null;
+        }
+
+        HistoricalNotification n = readNotificationFromParcel(mParcel);
+
+        mIndex++;
+        if (!hasNextNotification()) {
+            mParcel.recycle();
+            mParcel = null;
+        }
+        return n;
+    }
+
+    /**
+     * Adds all of the pooled strings that have been read from disk
+     */
+    public void addPooledStrings(@NonNull List<String> strings) {
+        mStringsToWrite.addAll(strings);
+    }
+
+    /**
+     * Builds the pooled strings from pending notifications. Useful if the pooled strings on
+     * disk contains strings that aren't relevant to the notifications in our collection.
+     */
+    public void poolStringsFromNotifications() {
+        mStringsToWrite.clear();
+        for (int i = 0; i < mNotificationsToWrite.size(); i++) {
+            final HistoricalNotification notification = mNotificationsToWrite.get(i);
+            mStringsToWrite.add(notification.getPackage());
+            mStringsToWrite.add(notification.getChannelName());
+            mStringsToWrite.add(notification.getChannelId());
+        }
+    }
+
+    /**
+     * Used when populating a history from disk; adds an historical notification.
+     */
+    public void addNotificationToWrite(@NonNull HistoricalNotification notification) {
+        if (notification == null) {
+            return;
+        }
+        mNotificationsToWrite.add(notification);
+        mHistoryCount++;
+    }
+
+    /**
+     * Removes a package's historical notifications and regenerates the string pool
+     */
+    public void removeNotificationsFromWrite(String packageName) {
+        for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
+            if (packageName.equals(mNotificationsToWrite.get(i).getPackage())) {
+                mNotificationsToWrite.remove(i);
+            }
+        }
+        poolStringsFromNotifications();
+    }
+
+    /**
+     * Gets pooled strings in order to write them to disk
+     */
+    public @NonNull String[] getPooledStringsToWrite() {
+        String[] stringsToWrite = mStringsToWrite.toArray(new String[]{});
+        Arrays.sort(stringsToWrite);
+        return stringsToWrite;
+    }
+
+    /**
+     * Gets the historical notifications in order to write them to disk
+     */
+    public @NonNull List<HistoricalNotification> getNotificationsToWrite() {
+        return mNotificationsToWrite;
+    }
+
+    /**
+     * Gets the number of notifications in the collection
+     */
+    public int getHistoryCount() {
+        return mHistoryCount;
+    }
+
+    private int findStringIndex(String str) {
+        final int index = Arrays.binarySearch(mStringPool, str);
+        if (index < 0) {
+            throw new IllegalStateException("String '" + str + "' is not in the string pool");
+        }
+        return index;
+    }
+
+    /**
+     * Writes a single notification to the parcel. Modify this when updating member variables of
+     * {@link HistoricalNotification}.
+     */
+    private void writeNotificationToParcel(HistoricalNotification notification, Parcel p,
+            int flags) {
+        final int packageIndex;
+        if (notification.mPackage != null) {
+            packageIndex = findStringIndex(notification.mPackage);
+        } else {
+            packageIndex = -1;
+        }
+
+        final int channelNameIndex;
+        if (notification.getChannelName() != null) {
+            channelNameIndex = findStringIndex(notification.getChannelName());
+        } else {
+            channelNameIndex = -1;
+        }
+
+        final int channelIdIndex;
+        if (notification.getChannelId() != null) {
+            channelIdIndex = findStringIndex(notification.getChannelId());
+        } else {
+            channelIdIndex = -1;
+        }
+
+        p.writeInt(packageIndex);
+        p.writeInt(channelNameIndex);
+        p.writeInt(channelIdIndex);
+        p.writeInt(notification.getUid());
+        p.writeInt(notification.getUserId());
+        p.writeLong(notification.getPostedTimeMs());
+        p.writeString(notification.getTitle());
+        p.writeString(notification.getText());
+        notification.getIcon().writeToParcel(p, flags);
+    }
+
+    /**
+     * Reads a single notification from the parcel. Modify this when updating member variables of
+     * {@link HistoricalNotification}.
+     */
+    private HistoricalNotification readNotificationFromParcel(Parcel p) {
+        HistoricalNotification.Builder notificationOut = new HistoricalNotification.Builder();
+        final int packageIndex = p.readInt();
+        if (packageIndex >= 0) {
+            notificationOut.mPackage = mStringPool[packageIndex];
+        } else {
+            notificationOut.mPackage = null;
+        }
+
+        final int channelNameIndex = p.readInt();
+        if (channelNameIndex >= 0) {
+            notificationOut.setChannelName(mStringPool[channelNameIndex]);
+        } else {
+            notificationOut.setChannelName(null);
+        }
+
+        final int channelIdIndex = p.readInt();
+        if (channelIdIndex >= 0) {
+            notificationOut.setChannelId(mStringPool[channelIdIndex]);
+        } else {
+            notificationOut.setChannelId(null);
+        }
+
+        notificationOut.setUid(p.readInt());
+        notificationOut.setUserId(p.readInt());
+        notificationOut.setPostedTimeMs(p.readLong());
+        notificationOut.setTitle(p.readString());
+        notificationOut.setText(p.readString());
+        notificationOut.setIcon(Icon.CREATOR.createFromParcel(p));
+
+        return notificationOut.build();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        Parcel data = Parcel.obtain();
+        data.writeInt(mHistoryCount);
+        data.writeInt(mIndex);
+        if (mHistoryCount > 0) {
+            mStringPool = getPooledStringsToWrite();
+            data.writeStringArray(mStringPool);
+
+            if (!mNotificationsToWrite.isEmpty()) {
+                // typically system_server to a process
+
+                // Write out the events
+                Parcel p = Parcel.obtain();
+                try {
+                    p.setDataPosition(0);
+                    for (int i = 0; i < mHistoryCount; i++) {
+                        final HistoricalNotification notification = mNotificationsToWrite.get(i);
+                        writeNotificationToParcel(notification, p, flags);
+                    }
+
+                    final int listByteLength = p.dataPosition();
+
+                    // Write the total length of the data.
+                    data.writeInt(listByteLength);
+
+                    // Write our current position into the data.
+                    data.writeInt(0);
+
+                    // Write the data.
+                    data.appendFrom(p, 0, listByteLength);
+                } finally {
+                    p.recycle();
+                }
+
+            } else if (mParcel != null) {
+                // typically process to process as mNotificationsToWrite is not populated on
+                // unparcel.
+
+                // Write the total length of the data.
+                data.writeInt(mParcel.dataSize());
+
+                // Write out current position into the data.
+                data.writeInt(mParcel.dataPosition());
+
+                // Write the data.
+                data.appendFrom(mParcel, 0, mParcel.dataSize());
+            } else {
+                throw new IllegalStateException(
+                        "Either mParcel or mNotificationsToWrite must not be null");
+            }
+        }
+        // Data can be too large for a transact. Write the data as a Blob, which will be written to
+        // ashmem if too large.
+        dest.writeBlob(data.marshall());
+    }
+
+    public static final @NonNull Creator<NotificationHistory> CREATOR
+            = new Creator<NotificationHistory>() {
+        @Override
+        public NotificationHistory createFromParcel(Parcel source) {
+            return new NotificationHistory(source);
+        }
+
+        @Override
+        public NotificationHistory[] newArray(int size) {
+            return new NotificationHistory[size];
+        }
+    };
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 7de8793..17f1a07 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -53,6 +53,7 @@
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -136,7 +137,7 @@
     private boolean mNoPerms;
     private boolean mSingleUser;
 
-    private ThreadLocal<String> mCallingPackage;
+    private ThreadLocal<Pair<String, String>> mCallingPackage;
 
     private Transport mTransport = new Transport();
 
@@ -226,11 +227,13 @@
         }
 
         @Override
-        public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
-                @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
+        public Cursor query(String callingPkg, @Nullable String featureId, Uri uri,
+                @Nullable String[] projection, @Nullable Bundle queryArgs,
+                @Nullable ICancellationSignal cancellationSignal) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceReadPermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 // The caller has no access to the data, so return an empty cursor with
                 // the columns in the requested order. The caller may ask for an invalid
                 // column and we would not catch that but this is not a problem in practice.
@@ -246,7 +249,8 @@
                 // we have to execute the query as if allowed to get a cursor with the
                 // columns. We then use the column names to return an empty cursor.
                 Cursor cursor;
-                final String original = setCallingPackage(callingPkg);
+                final Pair<String, String> original = setCallingPackage(
+                        new Pair<>(callingPkg, featureId));
                 try {
                     cursor = mInterface.query(
                             uri, projection, queryArgs,
@@ -264,7 +268,8 @@
                 return new MatrixCursor(cursor.getColumnNames(), 0);
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "query");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.query(
                         uri, projection, queryArgs,
@@ -293,12 +298,15 @@
         }
 
         @Override
-        public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+        public Uri insert(String callingPkg, @Nullable String featureId, Uri uri,
+                ContentValues initialValues) {
             uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
-                final String original = setCallingPackage(callingPkg);
+            if (enforceWritePermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
+                final Pair<String, String> original = setCallingPackage(
+                        new Pair<>(callingPkg, featureId));
                 try {
                     return rejectInsert(uri, initialValues);
                 } finally {
@@ -306,7 +314,8 @@
                 }
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "insert");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return maybeAddUserId(mInterface.insert(uri, initialValues), userId);
             } catch (RemoteException e) {
@@ -318,14 +327,17 @@
         }
 
         @Override
-        public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+        public int bulkInsert(String callingPkg, @Nullable String featureId, Uri uri,
+                ContentValues[] initialValues) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceWritePermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.bulkInsert(uri, initialValues);
             } catch (RemoteException e) {
@@ -337,8 +349,8 @@
         }
 
         @Override
-        public ContentProviderResult[] applyBatch(String callingPkg, String authority,
-                ArrayList<ContentProviderOperation> operations)
+        public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String featureId,
+                String authority, ArrayList<ContentProviderOperation> operations)
                 throws OperationApplicationException {
             validateIncomingAuthority(authority);
             int numOperations = operations.size();
@@ -355,20 +367,21 @@
                     operations.set(i, operation);
                 }
                 if (operation.isReadOperation()) {
-                    if (enforceReadPermission(callingPkg, uri, null)
+                    if (enforceReadPermission(callingPkg, featureId, uri, null)
                             != AppOpsManager.MODE_ALLOWED) {
                         throw new OperationApplicationException("App op not allowed", 0);
                     }
                 }
                 if (operation.isWriteOperation()) {
-                    if (enforceWritePermission(callingPkg, uri, null)
+                    if (enforceWritePermission(callingPkg, featureId, uri, null)
                             != AppOpsManager.MODE_ALLOWED) {
                         throw new OperationApplicationException("App op not allowed", 0);
                     }
                 }
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 ContentProviderResult[] results = mInterface.applyBatch(authority,
                         operations);
@@ -390,14 +403,17 @@
         }
 
         @Override
-        public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+        public int delete(String callingPkg, @Nullable String featureId, Uri uri, String selection,
+                String[] selectionArgs) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceWritePermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "delete");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.delete(uri, selection, selectionArgs);
             } catch (RemoteException e) {
@@ -409,15 +425,17 @@
         }
 
         @Override
-        public int update(String callingPkg, Uri uri, ContentValues values, String selection,
-                String[] selectionArgs) {
+        public int update(String callingPkg, @Nullable String featureId, Uri uri,
+                ContentValues values, String selection, String[] selectionArgs) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceWritePermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "update");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.update(uri, values, selection, selectionArgs);
             } catch (RemoteException e) {
@@ -429,14 +447,15 @@
         }
 
         @Override
-        public ParcelFileDescriptor openFile(
-                String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal,
-                IBinder callerToken) throws FileNotFoundException {
+        public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId,
+                Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken)
+                throws FileNotFoundException {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            enforceFilePermission(callingPkg, uri, mode, callerToken);
+            enforceFilePermission(callingPkg, featureId, uri, mode, callerToken);
             Trace.traceBegin(TRACE_TAG_DATABASE, "openFile");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.openFile(
                         uri, mode, CancellationSignal.fromTransport(cancellationSignal));
@@ -449,14 +468,15 @@
         }
 
         @Override
-        public AssetFileDescriptor openAssetFile(
-                String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
+        public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String featureId,
+                Uri uri, String mode, ICancellationSignal cancellationSignal)
                 throws FileNotFoundException {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            enforceFilePermission(callingPkg, uri, mode, null);
+            enforceFilePermission(callingPkg, featureId, uri, mode, null);
             Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.openAssetFile(
                         uri, mode, CancellationSignal.fromTransport(cancellationSignal));
@@ -469,12 +489,13 @@
         }
 
         @Override
-        public Bundle call(String callingPkg, String authority, String method, @Nullable String arg,
-                @Nullable Bundle extras) {
+        public Bundle call(String callingPkg, @Nullable String featureId, String authority,
+                String method, @Nullable String arg, @Nullable Bundle extras) {
             validateIncomingAuthority(authority);
             Bundle.setDefusable(extras, true);
             Trace.traceBegin(TRACE_TAG_DATABASE, "call");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.call(authority, method, arg, extras);
             } catch (RemoteException e) {
@@ -501,14 +522,16 @@
         }
 
         @Override
-        public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
-                Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
+        public AssetFileDescriptor openTypedAssetFile(String callingPkg,
+                @Nullable String featureId, Uri uri, String mimeType, Bundle opts,
+                ICancellationSignal cancellationSignal) throws FileNotFoundException {
             Bundle.setDefusable(opts, true);
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            enforceFilePermission(callingPkg, uri, "r", null);
+            enforceFilePermission(callingPkg, featureId, uri, "r", null);
             Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.openTypedAssetFile(
                         uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
@@ -526,15 +549,17 @@
         }
 
         @Override
-        public Uri canonicalize(String callingPkg, Uri uri) {
+        public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri) {
             uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
-            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceReadPermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return null;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return maybeAddUserId(mInterface.canonicalize(uri), userId);
             } catch (RemoteException e) {
@@ -546,15 +571,17 @@
         }
 
         @Override
-        public Uri uncanonicalize(String callingPkg, Uri uri) {
+        public Uri uncanonicalize(String callingPkg, String featureId,  Uri uri) {
             uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
-            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceReadPermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return null;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return maybeAddUserId(mInterface.uncanonicalize(uri), userId);
             } catch (RemoteException e) {
@@ -566,15 +593,17 @@
         }
 
         @Override
-        public boolean refresh(String callingPkg, Uri uri, Bundle args,
+        public boolean refresh(String callingPkg, String featureId, Uri uri, Bundle args,
                 ICancellationSignal cancellationSignal) throws RemoteException {
             uri = validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
-            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+            if (enforceReadPermission(callingPkg, featureId, uri, null)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
             Trace.traceBegin(TRACE_TAG_DATABASE, "refresh");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.refresh(uri, args,
                         CancellationSignal.fromTransport(cancellationSignal));
@@ -585,11 +614,13 @@
         }
 
         @Override
-        public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+        public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri,
+                int uid, int modeFlags) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission");
-            final String original = setCallingPackage(callingPkg);
+            final Pair<String, String> original = setCallingPackage(
+                    new Pair<>(callingPkg, featureId));
             try {
                 return mInterface.checkUriPermission(uri, uid, modeFlags);
             } catch (RemoteException e) {
@@ -600,44 +631,47 @@
             }
         }
 
-        private void enforceFilePermission(String callingPkg, Uri uri, String mode,
-                IBinder callerToken) throws FileNotFoundException, SecurityException {
+        private void enforceFilePermission(String callingPkg, @Nullable String featureId, Uri uri,
+                String mode, IBinder callerToken) throws FileNotFoundException, SecurityException {
             if (mode != null && mode.indexOf('w') != -1) {
-                if (enforceWritePermission(callingPkg, uri, callerToken)
+                if (enforceWritePermission(callingPkg, featureId, uri, callerToken)
                         != AppOpsManager.MODE_ALLOWED) {
                     throw new FileNotFoundException("App op not allowed");
                 }
             } else {
-                if (enforceReadPermission(callingPkg, uri, callerToken)
+                if (enforceReadPermission(callingPkg, featureId, uri, callerToken)
                         != AppOpsManager.MODE_ALLOWED) {
                     throw new FileNotFoundException("App op not allowed");
                 }
             }
         }
 
-        private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken)
+        private int enforceReadPermission(String callingPkg, @Nullable String featureId, Uri uri,
+                IBinder callerToken)
                 throws SecurityException {
-            final int mode = enforceReadPermissionInner(uri, callingPkg, callerToken);
+            final int mode = enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
             if (mode != MODE_ALLOWED) {
                 return mode;
             }
 
-            return noteProxyOp(callingPkg, mReadOp);
+            return noteProxyOp(callingPkg, featureId, mReadOp);
         }
 
-        private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken)
+        private int enforceWritePermission(String callingPkg, String featureId, Uri uri,
+                IBinder callerToken)
                 throws SecurityException {
-            final int mode = enforceWritePermissionInner(uri, callingPkg, callerToken);
+            final int mode = enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
             if (mode != MODE_ALLOWED) {
                 return mode;
             }
 
-            return noteProxyOp(callingPkg, mWriteOp);
+            return noteProxyOp(callingPkg, featureId, mWriteOp);
         }
 
-        private int noteProxyOp(String callingPkg, int op) {
+        private int noteProxyOp(String callingPkg, String featureId, int op) {
             if (op != AppOpsManager.OP_NONE) {
-                int mode = mAppOpsManager.noteProxyOp(op, callingPkg);
+                int mode = mAppOpsManager.noteProxyOp(op, callingPkg, Binder.getCallingUid(),
+                        featureId, null);
                 return mode == MODE_DEFAULT ? MODE_IGNORED : mode;
             }
 
@@ -659,18 +693,19 @@
      * associated with that permission.
      */
     private int checkPermissionAndAppOp(String permission, String callingPkg,
-            IBinder callerToken) {
+            @Nullable String featureId, IBinder callerToken) {
         if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
                 callerToken) != PERMISSION_GRANTED) {
             return MODE_ERRORED;
         }
 
-        return mTransport.noteProxyOp(callingPkg, AppOpsManager.permissionToOpCode(permission));
+        return mTransport.noteProxyOp(callingPkg, featureId,
+                AppOpsManager.permissionToOpCode(permission));
     }
 
     /** {@hide} */
-    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         final Context context = getContext();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -684,7 +719,8 @@
         if (mExported && checkUser(pid, uid, context)) {
             final String componentPerm = getReadPermission();
             if (componentPerm != null) {
-                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, featureId,
+                        callerToken);
                 if (mode == MODE_ALLOWED) {
                     return MODE_ALLOWED;
                 } else {
@@ -703,7 +739,8 @@
                 for (PathPermission pp : pps) {
                     final String pathPerm = pp.getReadPermission();
                     if (pathPerm != null && pp.match(path)) {
-                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, callerToken);
+                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, featureId,
+                                callerToken);
                         if (mode == MODE_ALLOWED) {
                             return MODE_ALLOWED;
                         } else {
@@ -751,8 +788,8 @@
     }
 
     /** {@hide} */
-    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         final Context context = getContext();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -766,7 +803,8 @@
         if (mExported && checkUser(pid, uid, context)) {
             final String componentPerm = getWritePermission();
             if (componentPerm != null) {
-                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, featureId,
+                        callerToken);
                 if (mode == MODE_ALLOWED) {
                     return MODE_ALLOWED;
                 } else {
@@ -785,7 +823,8 @@
                 for (PathPermission pp : pps) {
                     final String pathPerm = pp.getWritePermission();
                     if (pathPerm != null && pp.match(path)) {
-                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, callerToken);
+                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, featureId,
+                                callerToken);
                         if (mode == MODE_ALLOWED) {
                             return MODE_ALLOWED;
                         } else {
@@ -851,11 +890,11 @@
     }
 
     /**
-     * Set the calling package, returning the current value (or {@code null})
+     * Set the calling package/feature, returning the current value (or {@code null})
      * which can be used later to restore the previous state.
      */
-    private String setCallingPackage(String callingPackage) {
-        final String original = mCallingPackage.get();
+    private Pair<String, String> setCallingPackage(Pair<String, String> callingPackage) {
+        final Pair<String, String> original = mCallingPackage.get();
         mCallingPackage.set(callingPackage);
         onCallingPackageChanged();
         return original;
@@ -876,16 +915,42 @@
      *             calling UID.
      */
     public final @Nullable String getCallingPackage() {
-        final String pkg = mCallingPackage.get();
+        final Pair<String, String> pkg = mCallingPackage.get();
         if (pkg != null) {
-            mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+            mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg.first);
+            return pkg.first;
         }
-        return pkg;
+
+        return null;
+    }
+
+    /**
+     * Return the feature in the package of the caller that initiated the request being
+     * processed on the current thread. Returns {@code null} if not currently processing
+     * a request of the request is for the default feature.
+     * <p>
+     * This will always return {@code null} when processing
+     * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+     *
+     * @see #getCallingPackage
+     */
+    public final @Nullable String getCallingFeatureId() {
+        final Pair<String, String> pkg = mCallingPackage.get();
+        if (pkg != null) {
+            return pkg.second;
+        }
+
+        return null;
     }
 
     /** {@hide} */
     public final @Nullable String getCallingPackageUnchecked() {
-        return mCallingPackage.get();
+        final Pair<String, String> pkg = mCallingPackage.get();
+        if (pkg != null) {
+            return pkg.first;
+        }
+
+        return null;
     }
 
     /** {@hide} */
@@ -899,10 +964,10 @@
         /** {@hide} */
         public final long binderToken;
         /** {@hide} */
-        public final String callingPackage;
+        public final Pair<String, String> callingPackage;
 
         /** {@hide} */
-        public CallingIdentity(long binderToken, String callingPackage) {
+        public CallingIdentity(long binderToken, Pair<String, String> callingPackage) {
             this.binderToken = binderToken;
             this.callingPackage = callingPackage;
         }
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 8a4330e..d2632e7 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -80,6 +80,7 @@
     private final IContentProvider mContentProvider;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final String mPackageName;
+    private final @Nullable String mFeatureId;
     private final String mAuthority;
     private final boolean mStable;
 
@@ -103,6 +104,7 @@
         mContentResolver = contentResolver;
         mContentProvider = contentProvider;
         mPackageName = contentResolver.mPackageName;
+        mFeatureId = contentResolver.mFeatureId;
 
         mAuthority = authority;
         mStable = stable;
@@ -193,7 +195,7 @@
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
             final Cursor cursor = mContentProvider.query(
-                    mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
+                    mPackageName, mFeatureId, uri, projection, queryArgs, remoteCancellationSignal);
             if (cursor == null) {
                 return null;
             }
@@ -253,7 +255,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.canonicalize(mPackageName, url);
+            return mContentProvider.canonicalize(mPackageName, mFeatureId, url);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -271,7 +273,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.uncanonicalize(mPackageName, url);
+            return mContentProvider.uncanonicalize(mPackageName, mFeatureId, url);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -296,7 +298,8 @@
                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
-            return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
+            return mContentProvider.refresh(mPackageName, mFeatureId, url, args,
+                    remoteCancellationSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -315,7 +318,8 @@
 
         beforeRemote();
         try {
-            return mContentProvider.checkUriPermission(mPackageName, uri, uid, modeFlags);
+            return mContentProvider.checkUriPermission(mPackageName, mFeatureId, uri, uid,
+                    modeFlags);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -334,7 +338,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.insert(mPackageName, url, initialValues);
+            return mContentProvider.insert(mPackageName, mFeatureId, url, initialValues);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -354,7 +358,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.bulkInsert(mPackageName, url, initialValues);
+            return mContentProvider.bulkInsert(mPackageName, mFeatureId, url, initialValues);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -373,7 +377,8 @@
 
         beforeRemote();
         try {
-            return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
+            return mContentProvider.delete(mPackageName, mFeatureId, url, selection,
+                    selectionArgs);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -392,7 +397,8 @@
 
         beforeRemote();
         try {
-            return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
+            return mContentProvider.update(mPackageName, mFeatureId, url, values, selection,
+                    selectionArgs);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -436,7 +442,8 @@
                 remoteSignal = mContentProvider.createCancellationSignal();
                 signal.setRemote(remoteSignal);
             }
-            return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
+            return mContentProvider.openFile(mPackageName, mFeatureId, url, mode, remoteSignal,
+                    null);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -480,7 +487,8 @@
                 remoteSignal = mContentProvider.createCancellationSignal();
                 signal.setRemote(remoteSignal);
             }
-            return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
+            return mContentProvider.openAssetFile(mPackageName, mFeatureId, url, mode,
+                    remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -521,7 +529,7 @@
                 signal.setRemote(remoteSignal);
             }
             return mContentProvider.openTypedAssetFile(
-                    mPackageName, uri, mimeTypeFilter, opts, remoteSignal);
+                    mPackageName, mFeatureId, uri, mimeTypeFilter, opts, remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -548,7 +556,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.applyBatch(mPackageName, authority, operations);
+            return mContentProvider.applyBatch(mPackageName, mFeatureId, authority, operations);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -574,7 +582,7 @@
 
         beforeRemote();
         try {
-            return mContentProvider.call(mPackageName, authority, method, arg, extras);
+            return mContentProvider.call(mPackageName, mFeatureId, authority, method, arg, extras);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index cd735d4..f082690 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -83,6 +83,7 @@
                     data.enforceInterface(IContentProvider.descriptor);
 
                     String callingPkg = data.readString();
+                    String callingFeatureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
 
                     // String[] projection
@@ -101,7 +102,8 @@
                     ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
                             data.readStrongBinder());
 
-                    Cursor cursor = query(callingPkg, url, projection, queryArgs, cancellationSignal);
+                    Cursor cursor = query(callingPkg, callingFeatureId, url, projection, queryArgs,
+                            cancellationSignal);
                     if (cursor != null) {
                         CursorToBulkCursorAdaptor adaptor = null;
 
@@ -148,10 +150,11 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues values = ContentValues.CREATOR.createFromParcel(data);
 
-                    Uri out = insert(callingPkg, url, values);
+                    Uri out = insert(callingPkg, featureId, url, values);
                     reply.writeNoException();
                     Uri.writeToParcel(reply, out);
                     return true;
@@ -161,10 +164,11 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
 
-                    int count = bulkInsert(callingPkg, url, values);
+                    int count = bulkInsert(callingPkg, featureId, url, values);
                     reply.writeNoException();
                     reply.writeInt(count);
                     return true;
@@ -174,6 +178,7 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     String authority = data.readString();
                     final int numOperations = data.readInt();
                     final ArrayList<ContentProviderOperation> operations =
@@ -181,8 +186,8 @@
                     for (int i = 0; i < numOperations; i++) {
                         operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
                     }
-                    final ContentProviderResult[] results = applyBatch(callingPkg, authority,
-                            operations);
+                    final ContentProviderResult[] results = applyBatch(callingPkg, featureId,
+                            authority, operations);
                     reply.writeNoException();
                     reply.writeTypedArray(results, 0);
                     return true;
@@ -192,11 +197,12 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String selection = data.readString();
                     String[] selectionArgs = data.readStringArray();
 
-                    int count = delete(callingPkg, url, selection, selectionArgs);
+                    int count = delete(callingPkg, featureId, url, selection, selectionArgs);
 
                     reply.writeNoException();
                     reply.writeInt(count);
@@ -207,12 +213,14 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues values = ContentValues.CREATOR.createFromParcel(data);
                     String selection = data.readString();
                     String[] selectionArgs = data.readStringArray();
 
-                    int count = update(callingPkg, url, values, selection, selectionArgs);
+                    int count = update(callingPkg, featureId, url, values, selection,
+                            selectionArgs);
 
                     reply.writeNoException();
                     reply.writeInt(count);
@@ -223,6 +231,7 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mode = data.readString();
                     ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
@@ -230,7 +239,7 @@
                     IBinder callerToken = data.readStrongBinder();
 
                     ParcelFileDescriptor fd;
-                    fd = openFile(callingPkg, url, mode, signal, callerToken);
+                    fd = openFile(callingPkg, featureId, url, mode, signal, callerToken);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -246,13 +255,14 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mode = data.readString();
                     ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
                             data.readStrongBinder());
 
                     AssetFileDescriptor fd;
-                    fd = openAssetFile(callingPkg, url, mode, signal);
+                    fd = openAssetFile(callingPkg, featureId, url, mode, signal);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -269,12 +279,14 @@
                     data.enforceInterface(IContentProvider.descriptor);
 
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     String authority = data.readString();
                     String method = data.readString();
                     String stringArg = data.readString();
                     Bundle args = data.readBundle();
 
-                    Bundle responseBundle = call(callingPkg, authority, method, stringArg, args);
+                    Bundle responseBundle = call(callingPkg, featureId, authority, method,
+                            stringArg, args);
 
                     reply.writeNoException();
                     reply.writeBundle(responseBundle);
@@ -297,6 +309,7 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mimeType = data.readString();
                     Bundle opts = data.readBundle();
@@ -304,7 +317,7 @@
                             data.readStrongBinder());
 
                     AssetFileDescriptor fd;
-                    fd = openTypedAssetFile(callingPkg, url, mimeType, opts, signal);
+                    fd = openTypedAssetFile(callingPkg, featureId, url, mimeType, opts, signal);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -330,9 +343,10 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
 
-                    Uri out = canonicalize(callingPkg, url);
+                    Uri out = canonicalize(callingPkg, featureId, url);
                     reply.writeNoException();
                     Uri.writeToParcel(reply, out);
                     return true;
@@ -342,9 +356,10 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
 
-                    Uri out = uncanonicalize(callingPkg, url);
+                    Uri out = uncanonicalize(callingPkg, featureId, url);
                     reply.writeNoException();
                     Uri.writeToParcel(reply, out);
                     return true;
@@ -353,12 +368,13 @@
                 case REFRESH_TRANSACTION: {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     Bundle args = data.readBundle();
                     ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
                             data.readStrongBinder());
 
-                    boolean out = refresh(callingPkg, url, args, signal);
+                    boolean out = refresh(callingPkg, featureId, url, args, signal);
                     reply.writeNoException();
                     reply.writeInt(out ? 0 : -1);
                     return true;
@@ -367,11 +383,12 @@
                 case CHECK_URI_PERMISSION_TRANSACTION: {
                     data.enforceInterface(IContentProvider.descriptor);
                     String callingPkg = data.readString();
+                    String featureId = data.readString();
                     Uri uri = Uri.CREATOR.createFromParcel(data);
                     int uid = data.readInt();
                     int modeFlags = data.readInt();
 
-                    int out = checkUriPermission(callingPkg, uri, uid, modeFlags);
+                    int out = checkUriPermission(callingPkg, featureId, uri, uid, modeFlags);
                     reply.writeNoException();
                     reply.writeInt(out);
                     return true;
@@ -407,8 +424,9 @@
     }
 
     @Override
-    public Cursor query(String callingPkg, Uri url, @Nullable String[] projection,
-            @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
+    public Cursor query(String callingPkg, @Nullable String featureId, Uri url,
+            @Nullable String[] projection, @Nullable Bundle queryArgs,
+            @Nullable ICancellationSignal cancellationSignal)
             throws RemoteException {
         BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
         Parcel data = Parcel.obtain();
@@ -417,6 +435,7 @@
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             int length = 0;
             if (projection != null) {
@@ -478,7 +497,8 @@
     }
 
     @Override
-    public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
+    public Uri insert(String callingPkg, @Nullable String featureId, Uri url,
+            ContentValues values) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -486,6 +506,7 @@
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             values.writeToParcel(data, 0);
 
@@ -501,13 +522,15 @@
     }
 
     @Override
-    public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
+    public int bulkInsert(String callingPkg, @Nullable String featureId, Uri url,
+            ContentValues[] values) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeTypedArray(values, 0);
 
@@ -523,14 +546,15 @@
     }
 
     @Override
-    public ContentProviderResult[] applyBatch(String callingPkg, String authority,
-            ArrayList<ContentProviderOperation> operations)
-                    throws RemoteException, OperationApplicationException {
+    public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String featureId,
+            String authority, ArrayList<ContentProviderOperation> operations)
+            throws RemoteException, OperationApplicationException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
             data.writeString(callingPkg);
+            data.writeString(featureId);
             data.writeString(authority);
             data.writeInt(operations.size());
             for (ContentProviderOperation operation : operations) {
@@ -549,14 +573,15 @@
     }
 
     @Override
-    public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
-            throws RemoteException {
+    public int delete(String callingPkg, @Nullable String featureId, Uri url, String selection,
+            String[] selectionArgs) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeString(selection);
             data.writeStringArray(selectionArgs);
@@ -573,14 +598,15 @@
     }
 
     @Override
-    public int update(String callingPkg, Uri url, ContentValues values, String selection,
-            String[] selectionArgs) throws RemoteException {
+    public int update(String callingPkg, @Nullable String featureId, Uri url,
+            ContentValues values, String selection, String[] selectionArgs) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             values.writeToParcel(data, 0);
             data.writeString(selection);
@@ -598,8 +624,8 @@
     }
 
     @Override
-    public ParcelFileDescriptor openFile(
-            String callingPkg, Uri url, String mode, ICancellationSignal signal, IBinder token)
+    public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId, Uri url,
+            String mode, ICancellationSignal signal, IBinder token)
             throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -607,6 +633,7 @@
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeString(mode);
             data.writeStrongBinder(signal != null ? signal.asBinder() : null);
@@ -626,8 +653,8 @@
     }
 
     @Override
-    public AssetFileDescriptor openAssetFile(
-            String callingPkg, Uri url, String mode, ICancellationSignal signal)
+    public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String featureId,
+            Uri url, String mode, ICancellationSignal signal)
             throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -635,6 +662,7 @@
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeString(mode);
             data.writeStrongBinder(signal != null ? signal.asBinder() : null);
@@ -653,14 +681,15 @@
     }
 
     @Override
-    public Bundle call(String callingPkg, String authority, String method, String request,
-            Bundle args) throws RemoteException {
+    public Bundle call(String callingPkg, @Nullable String featureId, String authority,
+            String method, String request, Bundle args) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             data.writeString(authority);
             data.writeString(method);
             data.writeString(request);
@@ -700,14 +729,16 @@
     }
 
     @Override
-    public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
-            Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+    public AssetFileDescriptor openTypedAssetFile(String callingPkg, @Nullable String featureId,
+            Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+            throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeString(mimeType);
             data.writeBundle(opts);
@@ -747,14 +778,15 @@
     }
 
     @Override
-    public Uri canonicalize(String callingPkg, Uri url) throws RemoteException
-    {
+    public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri url)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
 
             mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0);
@@ -769,13 +801,15 @@
     }
 
     @Override
-    public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException {
+    public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri url)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
 
             mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0);
@@ -790,14 +824,15 @@
     }
 
     @Override
-    public boolean refresh(String callingPkg, Uri url, Bundle args, ICancellationSignal signal)
-            throws RemoteException {
+    public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle args,
+            ICancellationSignal signal) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeBundle(args);
             data.writeStrongBinder(signal != null ? signal.asBinder() : null);
@@ -814,14 +849,15 @@
     }
 
     @Override
-    public int checkUriPermission(String callingPkg, Uri url, int uid, int modeFlags)
-            throws RemoteException {
+    public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri url, int uid,
+            int modeFlags) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
             data.writeString(callingPkg);
+            data.writeString(featureId);
             url.writeToParcel(data, 0);
             data.writeInt(uid);
             data.writeInt(modeFlags);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 7f9ea76..2657cc5 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -649,6 +649,7 @@
     public ContentResolver(@Nullable Context context, @Nullable ContentInterface wrapped) {
         mContext = context != null ? context : ActivityThread.currentApplication();
         mPackageName = mContext.getOpPackageName();
+        mFeatureId = mContext.getFeatureId();
         mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
         mWrapped = wrapped;
     }
@@ -968,7 +969,7 @@
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
             try {
-                qCursor = unstableProvider.query(mPackageName, uri, projection,
+                qCursor = unstableProvider.query(mPackageName, mFeatureId, uri, projection,
                         queryArgs, remoteCancellationSignal);
             } catch (DeadObjectException e) {
                 // The remote process has died...  but we only hold an unstable
@@ -979,8 +980,8 @@
                 if (stableProvider == null) {
                     return null;
                 }
-                qCursor = stableProvider.query(
-                        mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
+                qCursor = stableProvider.query(mPackageName, mFeatureId, uri, projection,
+                        queryArgs, remoteCancellationSignal);
             }
             if (qCursor == null) {
                 return null;
@@ -1070,7 +1071,7 @@
         }
 
         try {
-            return provider.canonicalize(mPackageName, url);
+            return provider.canonicalize(mPackageName, mFeatureId, url);
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
             // Manager will kill this process shortly anyway.
@@ -1114,7 +1115,7 @@
         }
 
         try {
-            return provider.uncanonicalize(mPackageName, url);
+            return provider.uncanonicalize(mPackageName, mFeatureId, url);
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
             // Manager will kill this process shortly anyway.
@@ -1163,7 +1164,8 @@
                 remoteCancellationSignal = provider.createCancellationSignal();
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
-            return provider.refresh(mPackageName, url, args, remoteCancellationSignal);
+            return provider.refresh(mPackageName, mFeatureId, url, args,
+                    remoteCancellationSignal);
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
             // Manager will kill this process shortly anyway.
@@ -1564,7 +1566,7 @@
 
                     try {
                         fd = unstableProvider.openAssetFile(
-                                mPackageName, uri, mode, remoteCancellationSignal);
+                                mPackageName, mFeatureId, uri, mode, remoteCancellationSignal);
                         if (fd == null) {
                             // The provider will be released by the finally{} clause
                             return null;
@@ -1579,7 +1581,7 @@
                             throw new FileNotFoundException("No content provider: " + uri);
                         }
                         fd = stableProvider.openAssetFile(
-                                mPackageName, uri, mode, remoteCancellationSignal);
+                                mPackageName, mFeatureId, uri, mode, remoteCancellationSignal);
                         if (fd == null) {
                             // The provider will be released by the finally{} clause
                             return null;
@@ -1730,7 +1732,7 @@
 
             try {
                 fd = unstableProvider.openTypedAssetFile(
-                        mPackageName, uri, mimeType, opts, remoteCancellationSignal);
+                        mPackageName, mFeatureId, uri, mimeType, opts, remoteCancellationSignal);
                 if (fd == null) {
                     // The provider will be released by the finally{} clause
                     return null;
@@ -1745,7 +1747,7 @@
                     throw new FileNotFoundException("No content provider: " + uri);
                 }
                 fd = stableProvider.openTypedAssetFile(
-                        mPackageName, uri, mimeType, opts, remoteCancellationSignal);
+                        mPackageName, mFeatureId, uri, mimeType, opts, remoteCancellationSignal);
                 if (fd == null) {
                     // The provider will be released by the finally{} clause
                     return null;
@@ -1870,7 +1872,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            Uri createdRow = provider.insert(mPackageName, url, values);
+            Uri createdRow = provider.insert(mPackageName, mFeatureId, url, values);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
             return createdRow;
@@ -1951,7 +1953,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsCreated = provider.bulkInsert(mPackageName, url, values);
+            int rowsCreated = provider.bulkInsert(mPackageName, mFeatureId, url, values);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
             return rowsCreated;
@@ -1991,7 +1993,8 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
+            int rowsDeleted = provider.delete(mPackageName, mFeatureId, url, where,
+                    selectionArgs);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
             return rowsDeleted;
@@ -2035,7 +2038,8 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
+            int rowsUpdated = provider.update(mPackageName, mFeatureId, uri, values, where,
+                    selectionArgs);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
             return rowsUpdated;
@@ -2084,7 +2088,8 @@
             throw new IllegalArgumentException("Unknown authority " + authority);
         }
         try {
-            final Bundle res = provider.call(mPackageName, authority, method, arg, extras);
+            final Bundle res = provider.call(mPackageName, mFeatureId, authority, method, arg,
+                    extras);
             Bundle.setDefusable(res, true);
             return res;
         } catch (RemoteException e) {
@@ -3436,6 +3441,11 @@
         return mPackageName;
     }
 
+    /** @hide */
+    public @Nullable String getFeatureId() {
+        return mFeatureId;
+    }
+
     @UnsupportedAppUsage
     private static volatile IContentService sContentService;
     @UnsupportedAppUsage
@@ -3443,6 +3453,7 @@
 
     @UnsupportedAppUsage
     final String mPackageName;
+    final @Nullable String mFeatureId;
     final int mTargetSdkVersion;
     final ContentInterface mWrapped;
 
@@ -3638,19 +3649,19 @@
             orientation.value = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
             return afd;
         }), (ImageDecoder decoder, ImageInfo info, Source source) -> {
-            decoder.setAllocator(allocator);
+                decoder.setAllocator(allocator);
 
-            // One last-ditch check to see if we've been canceled.
-            if (signal != null) signal.throwIfCanceled();
+                // One last-ditch check to see if we've been canceled.
+                if (signal != null) signal.throwIfCanceled();
 
-            // We requested a rough thumbnail size, but the remote size may have
-            // returned something giant, so defensively scale down as needed.
-            final int widthSample = info.getSize().getWidth() / size.getWidth();
-            final int heightSample = info.getSize().getHeight() / size.getHeight();
-            final int sample = Math.min(widthSample, heightSample);
-            if (sample > 1) {
-                decoder.setTargetSampleSize(sample);
-            }
+                // We requested a rough thumbnail size, but the remote size may have
+                // returned something giant, so defensively scale down as needed.
+                final int widthSample = info.getSize().getWidth() / size.getWidth();
+                final int heightSample = info.getSize().getHeight() / size.getHeight();
+                final int sample = Math.max(widthSample, heightSample);
+                if (sample > 1) {
+                    decoder.setTargetSampleSize(sample);
+                }
         });
 
         // Transform the bitmap if requested. We use a side-channel to
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index fade0ab..d2c97c4 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,7 +16,6 @@
 
 package android.content;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.res.AssetFileDescriptor;
@@ -38,66 +37,96 @@
  * @hide
  */
 public interface IContentProvider extends IInterface {
-    public Cursor query(String callingPkg, Uri url, @Nullable String[] projection,
+    public Cursor query(String callingPkg, @Nullable String featureId, Uri url,
+            @Nullable String[] projection,
             @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
             throws RemoteException;
     public String getType(Uri url) throws RemoteException;
-    @UnsupportedAppUsage
-    public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
-            throws RemoteException;
-    @UnsupportedAppUsage
-    public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
-            throws RemoteException;
-    @UnsupportedAppUsage
-    public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
-            throws RemoteException;
-    @UnsupportedAppUsage
-    public int update(String callingPkg, Uri url, ContentValues values, String selection,
-            String[] selectionArgs) throws RemoteException;
-    public ParcelFileDescriptor openFile(
-            String callingPkg, Uri url, String mode, ICancellationSignal signal,
-            IBinder callerToken)
-            throws RemoteException, FileNotFoundException;
-    public AssetFileDescriptor openAssetFile(
-            String callingPkg, Uri url, String mode, ICancellationSignal signal)
-            throws RemoteException, FileNotFoundException;
-
     @Deprecated
-    public default ContentProviderResult[] applyBatch(String callingPkg,
-            ArrayList<ContentProviderOperation> operations)
-                    throws RemoteException, OperationApplicationException {
-        return applyBatch(callingPkg, "unknown", operations);
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} "
+            + "instead")
+    public default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
+            throws RemoteException {
+        return insert(callingPkg, null, url, initialValues);
     }
+    public Uri insert(String callingPkg, String featureId, Uri url, ContentValues initialValues)
+            throws RemoteException;
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "ContentProviderClient#bulkInsert(android.net.Uri, android.content.ContentValues[])"
+            + "} instead")
+    public default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
+            throws RemoteException {
+        return bulkInsert(callingPkg, null, url, initialValues);
+    }
+    public int bulkInsert(String callingPkg, String featureId, Uri url,
+            ContentValues[] initialValues) throws RemoteException;
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "ContentProviderClient#delete(android.net.Uri, java.lang.String, java.lang"
+            + ".String[])} instead")
+    public default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+            throws RemoteException {
+        return delete(callingPkg, null, url, selection, selectionArgs);
+    }
+    public int delete(String callingPkg, String featureId, Uri url, String selection,
+            String[] selectionArgs) throws RemoteException;
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "ContentProviderClient#update(android.net.Uri, android.content.ContentValues, java"
+            + ".lang.String, java.lang.String[])} instead")
+    public default int update(String callingPkg, Uri url, ContentValues values, String selection,
+            String[] selectionArgs) throws RemoteException {
+        return update(callingPkg, null, url, values, selection, selectionArgs);
+    }
+    public int update(String callingPkg, String featureId, Uri url, ContentValues values,
+            String selection, String[] selectionArgs) throws RemoteException;
 
-    public ContentProviderResult[] applyBatch(String callingPkg, String authority,
-            ArrayList<ContentProviderOperation> operations)
+    public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId, Uri url,
+            String mode, ICancellationSignal signal, IBinder callerToken)
+            throws RemoteException, FileNotFoundException;
+
+    public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String featureId,
+            Uri url, String mode, ICancellationSignal signal)
+            throws RemoteException, FileNotFoundException;
+
+    public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String featureId,
+            String authority, ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException;
 
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "ContentProviderClient#call(java.lang.String, java.lang.String, android.os.Bundle)} "
+            + "instead")
     public default Bundle call(String callingPkg, String method,
             @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
-        return call(callingPkg, "unknown", method, arg, extras);
+        return call(callingPkg, null, "unknown", method, arg, extras);
     }
 
-    public Bundle call(String callingPkg, String authority, String method,
-            @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
+    public Bundle call(String callingPkg, @Nullable String featureId, String authority,
+            String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
 
-    public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags)
-            throws RemoteException;
+    public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri, int uid,
+            int modeFlags) throws RemoteException;
 
     public ICancellationSignal createCancellationSignal() throws RemoteException;
 
-    public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
-    public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
+    public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+            throws RemoteException;
 
-    public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args,
-            ICancellationSignal cancellationSignal) throws RemoteException;
+    public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+            throws RemoteException;
+
+    public boolean refresh(String callingPkg, @Nullable String featureId, Uri url,
+            @Nullable Bundle args, ICancellationSignal cancellationSignal) throws RemoteException;
 
     // Data interchange.
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
-    public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
-            Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException;
+
+    public AssetFileDescriptor openTypedAssetFile(String callingPkg, @Nullable String featureId,
+            Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+            throws RemoteException, FileNotFoundException;
 
     /* IPC constants */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index f641731..124b6c6 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -86,6 +86,10 @@
         throw new RemoteException();
     }
 
+    public boolean isDeclared(String name) throws RemoteException {
+        throw new RemoteException();
+    }
+
     /**
      * Same as mServiceManager but used by apps.
      *
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 71b94ed..b7a3c8f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1314,7 +1314,8 @@
     }
 
     /**
-     * Returns whether switching users is currently allowed.
+     * Returns whether switching users is currently allowed for the user this process is running
+     * under.
      * <p>
      * Switching users is not allowed in the following cases:
      * <li>the user is in a phone call</li>
@@ -1329,10 +1330,24 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public @UserSwitchabilityResult int getUserSwitchability() {
-        final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
-                mContext.getContentResolver(),
-                Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
-        final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
+        return getUserSwitchability(Process.myUserHandle());
+    }
+
+    /**
+     * Returns whether switching users is currently allowed for the provided user.
+     * <p>
+     * Switching users is not allowed in the following cases:
+     * <li>the user is in a phone call</li>
+     * <li>{@link #DISALLOW_USER_SWITCH} is set</li>
+     * <li>system user hasn't been unlocked yet</li>
+     *
+     * @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
+     * @hide
+     */
+    @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
         final TelephonyManager tm =
                 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
@@ -1340,12 +1355,22 @@
         if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
             flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
         }
-        if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+        if (hasUserRestriction(DISALLOW_USER_SWITCH, userHandle)) {
             flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
         }
-        if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
-            flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+
+        // System User is always unlocked in Headless System User Mode, so ignore this flag
+        if (!isHeadlessSystemUserMode()) {
+            final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+                    mContext.getContentResolver(),
+                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+            final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
+
+            if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+                flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+            }
         }
+
         return flags;
     }
 
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 2143a0d..a80153d 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1081,7 +1081,8 @@
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
             // MANAGE_DOCUMENTS or associated URI permission here instead
             final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
-            enforceWritePermissionInner(rootUri, getCallingPackage(), null);
+            enforceWritePermissionInner(rootUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final String rootId = DocumentsContract.getRootId(rootUri);
             ejectRoot(rootId);
@@ -1102,7 +1103,8 @@
         enforceTree(documentUri);
 
         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
-            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+            enforceReadPermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
             final String childAuthority = childUri.getAuthority();
@@ -1114,7 +1116,8 @@
                             && isChildDocument(documentId, childId));
 
         } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
@@ -1128,7 +1131,8 @@
             out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
 
         } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
             final IntentSender intentSender = createWebLinkIntent(documentId, options);
@@ -1136,7 +1140,8 @@
             out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
 
         } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
             final String newDocumentId = renameDocument(documentId, displayName);
@@ -1160,7 +1165,8 @@
             }
 
         } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
             deleteDocument(documentId);
 
             // Document no longer exists, clean up any grants.
@@ -1170,8 +1176,10 @@
             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
             final String targetId = DocumentsContract.getDocumentId(targetUri);
 
-            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
-            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+            enforceReadPermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final String newDocumentId = copyDocument(documentId, targetId);
 
@@ -1194,9 +1202,12 @@
             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
             final String targetId = DocumentsContract.getDocumentId(targetUri);
 
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
-            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
-            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
+            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
 
             final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
 
@@ -1217,8 +1228,10 @@
             final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
 
-            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
-            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                    null);
             removeDocument(documentId, parentSourceId);
 
             // It's responsibility of the provider to revoke any grants, as the document may be
@@ -1227,7 +1240,8 @@
             final boolean isTreeUri = isTreeUri(documentUri);
 
             if (isTreeUri) {
-                enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+                enforceReadPermissionInner(documentUri, getCallingPackage(), getCallingFeatureId(),
+                        null);
             } else {
                 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
             }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index aeed20d..5331cb40 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1722,6 +1722,20 @@
             = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
 
     /**
+     * Activity Action: Show Work Policy info.
+     * DPC apps can implement an activity that handles this intent in order to show device policies
+     * associated with the work profile or managed device.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SHOW_WORK_POLICY_INFO =
+            "android.settings.SHOW_WORK_POLICY_INFO";
+
+    /**
      * Activity Action: Show screen that let user select its Autofill Service.
      * <p>
      * Input: Intent's data URI set with an application name, using the
@@ -2292,8 +2306,8 @@
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
                 IContentProvider cp = mProviderHolder.getProvider(cr);
-                cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
-                        mCallSetCommand, name, arg);
+                cp.call(cr.getPackageName(), cr.getFeatureId(),
+                        mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                 return false;
@@ -2366,14 +2380,15 @@
                     if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
                         final long token = Binder.clearCallingIdentity();
                         try {
-                            b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
-                                    mCallGetCommand, name, args);
+                            b = cp.call(cr.getPackageName(), cr.getFeatureId(),
+                                    mProviderHolder.mUri.getAuthority(), mCallGetCommand, name,
+                                    args);
                         } finally {
                             Binder.restoreCallingIdentity(token);
                         }
                     } else {
-                        b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
-                                mCallGetCommand, name, args);
+                        b = cp.call(cr.getPackageName(), cr.getFeatureId(),
+                                mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args);
                     }
                     if (b != null) {
                         String value = b.getString(Settings.NameValueTable.VALUE);
@@ -2441,14 +2456,14 @@
                 if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
-                                null);
+                        c = cp.query(cr.getPackageName(), cr.getFeatureId(), mUri,
+                                SELECT_VALUE_PROJECTION, queryArgs, null);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
                 } else {
-                    c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
-                            null);
+                    c = cp.query(cr.getPackageName(), cr.getFeatureId(), mUri,
+                            SELECT_VALUE_PROJECTION, queryArgs, null);
                 }
                 if (c == null) {
                     Log.w(TAG, "Can't get key " + name + " from " + mUri);
@@ -2543,8 +2558,8 @@
                 }
 
                 // Fetch all flags for the namespace at once for caching purposes
-                Bundle b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
-                        mCallListCommand, null, args);
+                Bundle b = cp.call(cr.getPackageName(), cr.getFeatureId(),
+                        mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
                 if (b == null) {
                     // Invalid response, return an empty map
                     return keyValues;
@@ -5118,8 +5133,8 @@
                 }
                 arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
-                cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
-                        CALL_METHOD_RESET_SECURE, null, arg);
+                cp.call(resolver.getPackageName(), resolver.getFeatureId(),
+                        sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_SECURE, null, arg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
             }
@@ -8254,6 +8269,12 @@
         public static final String AWARE_LOCK_ENABLED = "aware_lock_enabled";
 
         /**
+         * Controls whether tap gesture is enabled.
+         * @hide
+         */
+        public static final String TAP_GESTURE = "tap_gesture";
+
+        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -12807,8 +12828,8 @@
                 }
                 arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
-                cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
-                        CALL_METHOD_RESET_GLOBAL, null, arg);
+                cp.call(resolver.getPackageName(), resolver.getFeatureId(),
+                        sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_GLOBAL, null, arg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
             }
@@ -13744,8 +13765,8 @@
                     arg.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
                 }
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
-                cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
-                        CALL_METHOD_RESET_CONFIG, null, arg);
+                cp.call(resolver.getPackageName(), resolver.getFeatureId(),
+                        sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
             }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2f0a4eb..59e9ed1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -712,6 +712,8 @@
         mSurfaceAlpha = 1f;
 
         synchronized (mSurfaceControlLock) {
+            mSurface.release();
+
             if (mRtHandlingPositionUpdates) {
                 mRtReleaseSurfaces = true;
                 return;
@@ -725,7 +727,6 @@
                 mTmpTransaction.remove(mBackgroundControl);
                 mBackgroundControl = null;
             }
-            mSurface.release();
             mTmpTransaction.apply();
         }
     }
@@ -1198,7 +1199,6 @@
                     mRtTransaction.remove(mBackgroundControl);
                     mSurfaceControl = null;
                     mBackgroundControl = null;
-                    mSurface.release();
                 }
                 mRtHandlingPositionUpdates = false;
             }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 20dc234..85bf19f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -439,7 +439,6 @@
     boolean mReportNextDraw;
     boolean mFullRedrawNeeded;
     boolean mNewSurfaceNeeded;
-    boolean mHasHadWindowFocus;
     boolean mLastWasImTarget;
     boolean mForceNextWindowRelayout;
     CountDownLatch mWindowDrawCountDown;
@@ -2123,11 +2122,6 @@
                 endDragResizing();
                 destroyHardwareResources();
             }
-            if (viewVisibility == View.GONE) {
-                // After making a window gone, we will count it as being
-                // shown for the first time the next time it gets focus.
-                mHasHadWindowFocus = false;
-            }
         }
 
         // Non-visible windows can't hold accessibility focus.
@@ -2823,8 +2817,7 @@
                 if (imm != null && imTarget) {
                     imm.onPreWindowFocus(mView, hasWindowFocus);
                     imm.onPostWindowFocus(mView, mView.findFocus(),
-                            mWindowAttributes.softInputMode,
-                            !mHasHadWindowFocus, mWindowAttributes.flags);
+                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
                 }
             }
         }
@@ -3017,8 +3010,7 @@
             if (hasWindowFocus) {
                 if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                     imm.onPostWindowFocus(mView, mView.findFocus(),
-                            mWindowAttributes.softInputMode,
-                            !mHasHadWindowFocus, mWindowAttributes.flags);
+                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
                 }
                 // Clear the forward bit.  We can just do this directly, since
                 // the window manager doesn't care about it.
@@ -3028,7 +3020,6 @@
                         .softInputMode &=
                         ~WindowManager.LayoutParams
                                 .SOFT_INPUT_IS_FORWARD_NAVIGATION;
-                mHasHadWindowFocus = true;
 
                 // Refocusing a window that has a focused view should fire a
                 // focus event for the view since the global focused view changed.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6420d71..7ee53f2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -358,7 +358,7 @@
     boolean mActive = false;
 
     /**
-     * {@code true} if next {@link #onPostWindowFocus(View, View, int, boolean, int)} needs to
+     * {@code true} if next {@link #onPostWindowFocus(View, View, int, int)} needs to
      * restart input.
      */
     boolean mRestartOnNextWindowFocus = true;
@@ -1925,13 +1925,12 @@
      * @hide
      */
     public void onPostWindowFocus(View rootView, View focusedView,
-            @SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
+            @SoftInputModeFlags int softInputMode, int windowFlags) {
         boolean forceNewFocus = false;
         synchronized (mH) {
             if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
                     + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
-                    + " first=" + first + " flags=#"
-                    + Integer.toHexString(windowFlags));
+                    + " flags=#" + Integer.toHexString(windowFlags));
             if (mRestartOnNextWindowFocus) {
                 if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
                 mRestartOnNextWindowFocus = false;
@@ -1947,9 +1946,6 @@
                 startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
             }
         }
-        if (first) {
-            startInputFlags |= StartInputFlags.FIRST_WINDOW_FOCUS_GAIN;
-        }
 
         if (checkFocusNoStartInput(forceNewFocus)) {
             // We need to restart input on the current focus view.  This
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 3b82f18..b1752a4 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -45,8 +45,6 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LabeledIntent;
-import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
@@ -61,9 +59,7 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -85,7 +81,6 @@
 import android.service.chooser.ChooserTargetService;
 import android.service.chooser.IChooserTargetResult;
 import android.service.chooser.IChooserTargetService;
-import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.HashedStringCache;
@@ -110,6 +105,14 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.ResolverListAdapter.ViewHolder;
+import com.android.internal.app.chooser.ChooserTargetInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.NotSelectableTargetInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -124,7 +127,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.text.Collator;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -138,7 +140,9 @@
  * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
  *
  */
-public class ChooserActivity extends ResolverActivity {
+public class ChooserActivity extends ResolverActivity implements
+        ChooserListAdapter.ChooserListCommunicator,
+        SelectableTargetInfoCommunicator {
     private static final String TAG = "ChooserActivity";
 
 
@@ -154,12 +158,6 @@
 
     private static final boolean DEBUG = false;
 
-    /**
-     * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
-     * {@link AppPredictionManager} will be queried for direct share targets.
-     */
-    // TODO(b/123089490): Replace with system flag
-    private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
     private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
     // TODO(b/123088566) Share these in a better way.
     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
@@ -167,24 +165,21 @@
     private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
     public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
 
+    @VisibleForTesting
+    public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
+
     private boolean mIsAppPredictorComponentAvailable;
     private AppPredictor mAppPredictor;
     private AppPredictor.Callback mAppPredictorCallback;
     private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
 
-    /**
-     * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
-     * binding to every ChooserTargetService implementation.
-     */
-    // TODO(b/121287573): Replace with a system flag (setprop?)
-    private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
-    private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
-
     public static final int TARGET_TYPE_DEFAULT = 0;
     public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
     public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
     public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
 
+    private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
     @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
             TARGET_TYPE_DEFAULT,
             TARGET_TYPE_CHOOSER_TARGET,
@@ -233,10 +228,6 @@
 
     private int mCurrAvailableWidth = 0;
 
-    /** {@link ChooserActivity#getBaseScore} */
-    public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
-    /** {@link ChooserActivity#getBaseScore} */
-    public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
     // TODO: Update to handle landscape instead of using static value
     private static final int MAX_RANKED_TARGETS = 4;
@@ -246,14 +237,9 @@
 
     private static final int MAX_LOG_RANK_POSITION = 12;
 
-    @VisibleForTesting
-    public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
-
     private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
     private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
 
-    private boolean mListViewDataChanged = false;
-
     @Retention(SOURCE)
     @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
     private @interface ContentPreviewType {
@@ -266,9 +252,6 @@
     private static final int CONTENT_PREVIEW_TEXT = 3;
     protected MetricsLogger mMetricsLogger;
 
-    // Sorted list of DisplayResolveInfos for the alphabetical app section.
-    private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
-
     private ContentPreviewCoordinator mPreviewCoord;
 
     private class ContentPreviewCoordinator {
@@ -645,8 +628,7 @@
                 if (isFinishing() || isDestroyed()) {
                     return;
                 }
-                // May be null if there are no apps to perform share/open action.
-                if (mChooserListAdapter == null) {
+                if (mChooserListAdapter.getCount() == 0) {
                     return;
                 }
                 if (resultList.isEmpty()) {
@@ -775,7 +757,7 @@
             @Override
             public void onSomePackagesChanged() {
                 mAdapter.handlePackagesChanged();
-                bindProfileView();
+                updateProfileViewButton();
             }
         };
     }
@@ -1191,7 +1173,7 @@
         }
     }
 
-    @Override
+    @Override // ResolverListCommunicator
     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
         Intent result = defIntent;
         if (mReplacementExtras != null) {
@@ -1231,9 +1213,8 @@
     }
 
     @Override
-    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
+    public void onPrepareAdapterView(AbsListView adapterView, ResolverListAdapter adapter) {
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
-        mChooserListAdapter = (ChooserListAdapter) adapter;
         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
             mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
                     TARGET_TYPE_DEFAULT);
@@ -1245,11 +1226,17 @@
     }
 
     @Override
+    protected boolean rebuildList() {
+        mChooserListAdapter = (ChooserListAdapter) mAdapter;
+        return rebuildListInternal();
+    }
+
+    @Override
     public int getLayoutResource() {
         return R.layout.chooser_grid;
     }
 
-    @Override
+    @Override // ResolverListCommunicator
     public boolean shouldGetActivityMetadata() {
         return true;
     }
@@ -1328,7 +1315,7 @@
         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
         super.startSelected(which, always, filtered);
 
-        if (mChooserListAdapter != null) {
+        if (mChooserListAdapter.getCount() > 0) {
             // Log the index of which type of target the user picked.
             // Lower values mean the ranking was better.
             int cat = 0;
@@ -1342,7 +1329,7 @@
                     // Log the package name + target name to answer the question if most users
                     // share to mostly the same person or to a bunch of different people.
                     ChooserTarget target =
-                            mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
+                            mChooserListAdapter.getChooserTargetForValue(value);
                     directTargetHashed = HashedStringCache.getInstance().hashString(
                             this,
                             TAG,
@@ -1428,7 +1415,7 @@
                 continue;
             }
             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
-            if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+            if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
                     && sm.hasShareTargets(ai.packageName)) {
                 // Share targets will be queried from ShortcutManager
                 continue;
@@ -1817,8 +1804,8 @@
      */
     @Nullable
     private AppPredictor getAppPredictorForDirectShareIfEnabled() {
-        return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
-                ? getAppPredictor() : null;
+        return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
+                && !ActivityManager.isLowRamDeviceStatic() ? getAppPredictor() : null;
     }
 
     /**
@@ -1900,24 +1887,18 @@
         }
     }
 
-    private void updateAlphabeticalList() {
-        mSortedList.clear();
-        mSortedList.addAll(getDisplayList());
-        Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
-    }
-
     /**
      * Sort intents alphabetically based on display label.
      */
-    class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
+    static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
         Collator mCollator;
         AzInfoComparator(Context context) {
             mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
         }
 
         @Override
-        public int compare(ResolverActivity.DisplayResolveInfo lhsp,
-                ResolverActivity.DisplayResolveInfo rhsp) {
+        public int compare(
+                DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
             return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
         }
     }
@@ -1955,12 +1936,12 @@
     }
 
     @Override
-    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
-            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-            boolean filterLastUsed) {
-        final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
-                initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
-        return adapter;
+    public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed, boolean useLayoutForBrowsables) {
+        return new ChooserListAdapter(context, payloadIntents,
+                initialIntents, rList, filterLastUsed, createListController(),
+                useLayoutForBrowsables, this, this);
     }
 
     @VisibleForTesting
@@ -1999,347 +1980,21 @@
         return null;
     }
 
-    interface ChooserTargetInfo extends TargetInfo {
-        float getModifiedScore();
-
-        ChooserTarget getChooserTarget();
-
-        /**
-          * Do not label as 'equals', since this doesn't quite work
-          * as intended with java 8.
-          */
-        default boolean isSimilar(ChooserTargetInfo other) {
-            if (other == null) return false;
-
-            ChooserTarget ct1 = getChooserTarget();
-            ChooserTarget ct2 = other.getChooserTarget();
-
-            // If either is null, there is not enough info to make an informed decision
-            // about equality, so just exit
-            if (ct1 == null || ct2 == null) return false;
-
-            if (ct1.getComponentName().equals(ct2.getComponentName())
-                    && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
-                    && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
-                return true;
-            }
-
-            return false;
-        }
-    }
-
-    /**
-      * Distinguish between targets that selectable by the user, vs those that are
-      * placeholders for the system while information is loading in an async manner.
-      */
-    abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
-
-        public Intent getResolvedIntent() {
-            return null;
-        }
-
-        public ComponentName getResolvedComponentName() {
-            return null;
-        }
-
-        public boolean start(Activity activity, Bundle options) {
-            return false;
-        }
-
-        public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
-            return false;
-        }
-
-        public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
-            return false;
-        }
-
-        public ResolveInfo getResolveInfo() {
-            return null;
-        }
-
-        public CharSequence getDisplayLabel() {
-            return null;
-        }
-
-        public CharSequence getExtendedInfo() {
-            return null;
-        }
-
-        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
-            return null;
-        }
-
-        public List<Intent> getAllSourceIntents() {
-            return null;
-        }
-
-        public float getModifiedScore() {
-            return -0.1f;
-        }
-
-        public ChooserTarget getChooserTarget() {
-            return null;
-        }
-
-        public boolean isSuspended() {
-            return false;
-        }
-    }
-
-    final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
-        public Drawable getDisplayIcon() {
+    static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
+        public Drawable getDisplayIcon(Context context) {
             AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
-                    getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
+                    context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
             avd.start(); // Start animation after generation
             return avd;
         }
     }
 
-
-    final class EmptyTargetInfo extends NotSelectableTargetInfo {
-        public Drawable getDisplayIcon() {
+    static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+        public Drawable getDisplayIcon(Context context) {
             return null;
         }
     }
 
-    final class SelectableTargetInfo implements ChooserTargetInfo {
-        private final DisplayResolveInfo mSourceInfo;
-        private final ResolveInfo mBackupResolveInfo;
-        private final ChooserTarget mChooserTarget;
-        private final String mDisplayLabel;
-        private Drawable mBadgeIcon = null;
-        private CharSequence mBadgeContentDescription;
-        private Drawable mDisplayIcon;
-        private final Intent mFillInIntent;
-        private final int mFillInFlags;
-        private final float mModifiedScore;
-        private boolean mIsSuspended = false;
-
-        SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
-                float modifiedScore) {
-            mSourceInfo = sourceInfo;
-            mChooserTarget = chooserTarget;
-            mModifiedScore = modifiedScore;
-            if (sourceInfo != null) {
-                final ResolveInfo ri = sourceInfo.getResolveInfo();
-                if (ri != null) {
-                    final ActivityInfo ai = ri.activityInfo;
-                    if (ai != null && ai.applicationInfo != null) {
-                        final PackageManager pm = getPackageManager();
-                        mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
-                        mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
-                        mIsSuspended =
-                                (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
-                    }
-                }
-            }
-            // TODO(b/121287224): do this in the background thread, and only for selected targets
-            mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
-
-            if (sourceInfo != null) {
-                mBackupResolveInfo = null;
-            } else {
-                mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
-            }
-
-            mFillInIntent = null;
-            mFillInFlags = 0;
-
-            mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
-        }
-
-        private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
-            mSourceInfo = other.mSourceInfo;
-            mBackupResolveInfo = other.mBackupResolveInfo;
-            mChooserTarget = other.mChooserTarget;
-            mBadgeIcon = other.mBadgeIcon;
-            mBadgeContentDescription = other.mBadgeContentDescription;
-            mDisplayIcon = other.mDisplayIcon;
-            mFillInIntent = fillInIntent;
-            mFillInFlags = flags;
-            mModifiedScore = other.mModifiedScore;
-
-            mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
-        }
-
-        private String sanitizeDisplayLabel(CharSequence label) {
-            SpannableStringBuilder sb = new SpannableStringBuilder(label);
-            sb.clearSpans();
-            return sb.toString();
-        }
-
-        public boolean isSuspended() {
-            return mIsSuspended;
-        }
-
-        /**
-         * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
-         * the call to LauncherApps#getShortcuts(ShortcutQuery).
-         */
-        // TODO(121287224): Refactor code to apply the suggestion above
-        private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
-            Drawable directShareIcon = null;
-
-            // First get the target drawable and associated activity info
-            final Icon icon = target.getIcon();
-            if (icon != null) {
-                directShareIcon = icon.loadDrawable(ChooserActivity.this);
-            } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
-                Bundle extras = target.getIntentExtras();
-                if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
-                    CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
-                    LauncherApps launcherApps = (LauncherApps) getSystemService(
-                            Context.LAUNCHER_APPS_SERVICE);
-                    final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
-                    q.setPackage(target.getComponentName().getPackageName());
-                    q.setShortcutIds(Arrays.asList(shortcutId.toString()));
-                    q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
-                    final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
-                    if (shortcuts != null && shortcuts.size() > 0) {
-                        directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
-                    }
-                }
-            }
-
-            if (directShareIcon == null) return null;
-
-            ActivityInfo info = null;
-            try {
-                info = mPm.getActivityInfo(target.getComponentName(), 0);
-            } catch (NameNotFoundException error) {
-                Log.e(TAG, "Could not find activity associated with ChooserTarget");
-            }
-
-            if (info == null) return null;
-
-            // Now fetch app icon and raster with no badging even in work profile
-            Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
-                    UserHandle.getUserHandleForUid(UserHandle.myUserId()));
-
-            // Raster target drawable with appIcon as a badge
-            SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
-            Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
-            sif.recycle();
-
-            return new BitmapDrawable(getResources(), directShareBadgedIcon);
-        }
-
-        public float getModifiedScore() {
-            return mModifiedScore;
-        }
-
-        @Override
-        public Intent getResolvedIntent() {
-            if (mSourceInfo != null) {
-                return mSourceInfo.getResolvedIntent();
-            }
-
-            final Intent targetIntent = new Intent(getTargetIntent());
-            targetIntent.setComponent(mChooserTarget.getComponentName());
-            targetIntent.putExtras(mChooserTarget.getIntentExtras());
-            return targetIntent;
-        }
-
-        @Override
-        public ComponentName getResolvedComponentName() {
-            if (mSourceInfo != null) {
-                return mSourceInfo.getResolvedComponentName();
-            } else if (mBackupResolveInfo != null) {
-                return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
-                        mBackupResolveInfo.activityInfo.name);
-            }
-            return null;
-        }
-
-        private Intent getBaseIntentToSend() {
-            Intent result = getResolvedIntent();
-            if (result == null) {
-                Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
-            } else {
-                result = new Intent(result);
-                if (mFillInIntent != null) {
-                    result.fillIn(mFillInIntent, mFillInFlags);
-                }
-                result.fillIn(mReferrerFillInIntent, 0);
-            }
-            return result;
-        }
-
-        @Override
-        public boolean start(Activity activity, Bundle options) {
-            throw new RuntimeException("ChooserTargets should be started as caller.");
-        }
-
-        @Override
-        public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
-            final Intent intent = getBaseIntentToSend();
-            if (intent == null) {
-                return false;
-            }
-            intent.setComponent(mChooserTarget.getComponentName());
-            intent.putExtras(mChooserTarget.getIntentExtras());
-
-            // Important: we will ignore the target security checks in ActivityManager
-            // if and only if the ChooserTarget's target package is the same package
-            // where we got the ChooserTargetService that provided it. This lets a
-            // ChooserTargetService provide a non-exported or permission-guarded target
-            // to the chooser for the user to pick.
-            //
-            // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
-            // so we'll obey the caller's normal security checks.
-            final boolean ignoreTargetSecurity = mSourceInfo != null
-                    && mSourceInfo.getResolvedComponentName().getPackageName()
-                    .equals(mChooserTarget.getComponentName().getPackageName());
-            return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
-        }
-
-        @Override
-        public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
-            throw new RuntimeException("ChooserTargets should be started as caller.");
-        }
-
-        @Override
-        public ResolveInfo getResolveInfo() {
-            return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
-        }
-
-        @Override
-        public CharSequence getDisplayLabel() {
-            return mDisplayLabel;
-        }
-
-        @Override
-        public CharSequence getExtendedInfo() {
-            // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
-            return null;
-        }
-
-        @Override
-        public Drawable getDisplayIcon() {
-            return mDisplayIcon;
-        }
-
-        public ChooserTarget getChooserTarget() {
-            return mChooserTarget;
-        }
-
-        @Override
-        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
-            return new SelectableTargetInfo(this, fillInIntent, flags);
-        }
-
-        @Override
-        public List<Intent> getAllSourceIntents() {
-            final List<Intent> results = new ArrayList<>();
-            if (mSourceInfo != null) {
-                // We only queried the service for the first one in our sourceinfo.
-                results.add(mSourceInfo.getAllSourceIntents().get(0));
-            }
-            return results;
-        }
-    }
-
     private void handleScroll(View view, int x, int y, int oldx, int oldy) {
         if (mChooserRowAdapter != null) {
             mChooserRowAdapter.handleScroll(view, y, oldy);
@@ -2408,7 +2063,8 @@
 
                 boolean isExpandable = getResources().getConfiguration().orientation
                         == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
-                if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
+                if (directShareHeight != 0 && isSendAction(getTargetIntent())
+                        && isExpandable) {
                     // make sure to leave room for direct share 4->8 expansion
                     int requiredExpansionHeight =
                             (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
@@ -2424,498 +2080,6 @@
         }
     }
 
-    public class ChooserListAdapter extends ResolveListAdapter {
-        public static final int TARGET_BAD = -1;
-        public static final int TARGET_CALLER = 0;
-        public static final int TARGET_SERVICE = 1;
-        public static final int TARGET_STANDARD = 2;
-        public static final int TARGET_STANDARD_AZ = 3;
-
-        private static final int MAX_SUGGESTED_APP_TARGETS = 4;
-        private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
-
-        private static final int MAX_SERVICE_TARGETS = 8;
-
-        private final int mMaxShortcutTargetsPerApp =
-                getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
-
-        private int mNumShortcutResults = 0;
-
-        // Reserve spots for incoming direct share targets by adding placeholders
-        private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
-        private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
-        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
-
-        private final BaseChooserTargetComparator mBaseTargetComparator
-                = new BaseChooserTargetComparator();
-
-        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
-                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-                boolean filterLastUsed, ResolverListController resolverListController) {
-            // Don't send the initial intents through the shared ResolverActivity path,
-            // we want to separate them into a different section.
-            super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
-                    resolverListController);
-
-            createPlaceHolders();
-
-            if (initialIntents != null) {
-                final PackageManager pm = getPackageManager();
-                for (int i = 0; i < initialIntents.length; i++) {
-                    final Intent ii = initialIntents[i];
-                    if (ii == null) {
-                        continue;
-                    }
-
-                    // We reimplement Intent#resolveActivityInfo here because if we have an
-                    // implicit intent, we want the ResolveInfo returned by PackageManager
-                    // instead of one we reconstruct ourselves. The ResolveInfo returned might
-                    // have extra metadata and resolvePackageName set and we want to respect that.
-                    ResolveInfo ri = null;
-                    ActivityInfo ai = null;
-                    final ComponentName cn = ii.getComponent();
-                    if (cn != null) {
-                        try {
-                            ai = pm.getActivityInfo(ii.getComponent(), 0);
-                            ri = new ResolveInfo();
-                            ri.activityInfo = ai;
-                        } catch (PackageManager.NameNotFoundException ignored) {
-                            // ai will == null below
-                        }
-                    }
-                    if (ai == null) {
-                        ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
-                        ai = ri != null ? ri.activityInfo : null;
-                    }
-                    if (ai == null) {
-                        Log.w(TAG, "No activity found for " + ii);
-                        continue;
-                    }
-                    UserManager userManager =
-                            (UserManager) getSystemService(Context.USER_SERVICE);
-                    if (ii instanceof LabeledIntent) {
-                        LabeledIntent li = (LabeledIntent) ii;
-                        ri.resolvePackageName = li.getSourcePackage();
-                        ri.labelRes = li.getLabelResource();
-                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
-                        ri.icon = li.getIconResource();
-                        ri.iconResourceId = ri.icon;
-                    }
-                    if (userManager.isManagedProfile()) {
-                        ri.noResourceId = true;
-                        ri.icon = 0;
-                    }
-                    mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii));
-                }
-            }
-        }
-
-        @Override
-        public void handlePackagesChanged() {
-            if (DEBUG) {
-                Log.d(TAG, "clearing queryTargets on package change");
-            }
-            createPlaceHolders();
-            mServicesRequested.clear();
-            notifyDataSetChanged();
-
-            super.handlePackagesChanged();
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            if (!mListViewDataChanged) {
-                mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
-                        LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
-                mListViewDataChanged = true;
-            }
-        }
-
-        private void refreshListView() {
-            if (mListViewDataChanged) {
-                super.notifyDataSetChanged();
-            }
-            mListViewDataChanged = false;
-        }
-
-
-        private void createPlaceHolders() {
-            mNumShortcutResults = 0;
-            mServiceTargets.clear();
-            for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
-                mServiceTargets.add(mPlaceHolderTargetInfo);
-            }
-        }
-
-        @Override
-        public View onCreateView(ViewGroup parent) {
-            return mInflater.inflate(
-                    com.android.internal.R.layout.resolve_grid_item, parent, false);
-        }
-
-        @Override
-        protected void onBindView(View view, TargetInfo info) {
-            super.onBindView(view, info);
-
-            // If target is loading, show a special placeholder shape in the label, make unclickable
-            final ViewHolder holder = (ViewHolder) view.getTag();
-            if (info instanceof PlaceHolderTargetInfo) {
-                final int maxWidth = getResources().getDimensionPixelSize(
-                        R.dimen.chooser_direct_share_label_placeholder_max_width);
-                holder.text.setMaxWidth(maxWidth);
-                holder.text.setBackground(getResources().getDrawable(
-                        R.drawable.chooser_direct_share_label_placeholder, getTheme()));
-                // Prevent rippling by removing background containing ripple
-                holder.itemView.setBackground(null);
-            } else {
-                holder.text.setMaxWidth(Integer.MAX_VALUE);
-                holder.text.setBackground(null);
-                holder.itemView.setBackground(holder.defaultItemViewBackground);
-            }
-        }
-
-        /**
-         * Rather than fully sorting the input list, this sorting task will put the top k elements
-         * in the head of input list and fill the tail with other elements in undetermined order.
-         */
-        @Override
-        AsyncTask<List<ResolvedComponentInfo>,
-                Void,
-                List<ResolvedComponentInfo>> createSortingTask() {
-            return new AsyncTask<List<ResolvedComponentInfo>,
-                    Void,
-                    List<ResolvedComponentInfo>>() {
-                @Override
-                protected List<ResolvedComponentInfo> doInBackground(
-                        List<ResolvedComponentInfo>... params) {
-                    mResolverListController.topK(params[0],
-                            getMaxRankedTargets());
-                    return params[0];
-                }
-
-                @Override
-                protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
-                    processSortedList(sortedComponents);
-                    bindProfileView();
-                    notifyDataSetChanged();
-                }
-            };
-        }
-
-        @Override
-        public void onListRebuilt() {
-            if (getDisplayList() == null || getDisplayList().isEmpty()) {
-                notifyDataSetChanged();
-            } else {
-                new AsyncTask<Void, Void, Void>() {
-                    @Override
-                    protected Void doInBackground(Void... voids) {
-                        updateAlphabeticalList();
-                        return null;
-                    }
-
-                    @Override
-                    protected void onPostExecute(Void aVoid) {
-                        notifyDataSetChanged();
-                    }
-                }.execute();
-            }
-
-            // don't support direct share on low ram devices
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                return;
-            }
-
-            if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
-                        || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
-                if (DEBUG) {
-                    Log.d(TAG, "querying direct share targets from ShortcutManager");
-                }
-
-                queryDirectShareTargets(this, false);
-            }
-            if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
-                if (DEBUG) {
-                    Log.d(TAG, "List built querying services");
-                }
-
-                queryTargetServices(this);
-            }
-        }
-
-        @Override
-        public boolean shouldGetResolvedFilter() {
-            return true;
-        }
-
-        @Override
-        public int getCount() {
-            return getRankedTargetCount() + getAlphaTargetCount()
-                    + getSelectableServiceTargetCount() + getCallerTargetCount();
-        }
-
-        @Override
-        public int getUnfilteredCount() {
-            int appTargets = super.getUnfilteredCount();
-            if (appTargets > getMaxRankedTargets()) {
-                appTargets = appTargets + getMaxRankedTargets();
-            }
-            return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
-        }
-
-
-        public int getCallerTargetCount() {
-            return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
-        }
-
-        /**
-          * Filter out placeholders and non-selectable service targets
-          */
-        public int getSelectableServiceTargetCount() {
-            int count = 0;
-            for (ChooserTargetInfo info : mServiceTargets) {
-                if (info instanceof SelectableTargetInfo) {
-                    count++;
-                }
-            }
-            return count;
-        }
-
-        public int getServiceTargetCount() {
-            if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
-                return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
-            }
-
-            return 0;
-        }
-
-        int getAlphaTargetCount() {
-            int standardCount = super.getCount();
-            return standardCount > getMaxRankedTargets() ? standardCount : 0;
-        }
-
-        int getRankedTargetCount() {
-            int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
-            return Math.min(spacesAvailable, super.getCount());
-        }
-
-        private int getMaxRankedTargets() {
-            return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
-        }
-
-        public int getPositionTargetType(int position) {
-            int offset = 0;
-
-            final int serviceTargetCount = getServiceTargetCount();
-            if (position < serviceTargetCount) {
-                return TARGET_SERVICE;
-            }
-            offset += serviceTargetCount;
-
-            final int callerTargetCount = getCallerTargetCount();
-            if (position - offset < callerTargetCount) {
-                return TARGET_CALLER;
-            }
-            offset += callerTargetCount;
-
-            final int rankedTargetCount = getRankedTargetCount();
-            if (position - offset < rankedTargetCount) {
-                return TARGET_STANDARD;
-            }
-            offset += rankedTargetCount;
-
-            final int standardTargetCount = getAlphaTargetCount();
-            if (position - offset < standardTargetCount) {
-                return TARGET_STANDARD_AZ;
-            }
-
-            return TARGET_BAD;
-        }
-
-        @Override
-        public TargetInfo getItem(int position) {
-            return targetInfoForPosition(position, true);
-        }
-
-
-        /**
-         * Find target info for a given position.
-         * Since ChooserActivity displays several sections of content, determine which
-         * section provides this item.
-         */
-        @Override
-        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
-            int offset = 0;
-
-            // Direct share targets
-            final int serviceTargetCount = filtered ? getServiceTargetCount() :
-                                               getSelectableServiceTargetCount();
-            if (position < serviceTargetCount) {
-                return mServiceTargets.get(position);
-            }
-            offset += serviceTargetCount;
-
-            // Targets provided by calling app
-            final int callerTargetCount = getCallerTargetCount();
-            if (position - offset < callerTargetCount) {
-                return mCallerTargets.get(position - offset);
-            }
-            offset += callerTargetCount;
-
-            // Ranked standard app targets
-            final int rankedTargetCount = getRankedTargetCount();
-            if (position - offset < rankedTargetCount) {
-                return filtered ? super.getItem(position - offset)
-                        : getDisplayResolveInfo(position - offset);
-            }
-            offset += rankedTargetCount;
-
-            // Alphabetical complete app target list.
-            if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
-                return mSortedList.get(position - offset);
-            }
-
-            return null;
-        }
-
-
-        /**
-         * Evaluate targets for inclusion in the direct share area. May not be included
-         * if score is too low.
-         */
-        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
-                @ShareTargetType int targetType) {
-            if (DEBUG) {
-                Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
-                        + " targets");
-            }
-
-            if (targets.size() == 0) {
-                return;
-            }
-
-            final float baseScore = getBaseScore(origTarget, targetType);
-            Collections.sort(targets, mBaseTargetComparator);
-
-            final boolean isShortcutResult =
-                    (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
-                            || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
-            final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
-                                       : MAX_CHOOSER_TARGETS_PER_APP;
-            float lastScore = 0;
-            boolean shouldNotify = false;
-            for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
-                final ChooserTarget target = targets.get(i);
-                float targetScore = target.getScore();
-                targetScore *= baseScore;
-                if (i > 0 && targetScore >= lastScore) {
-                    // Apply a decay so that the top app can't crowd out everything else.
-                    // This incents ChooserTargetServices to define what's truly better.
-                    targetScore = lastScore * 0.95f;
-                }
-                boolean isInserted = insertServiceTarget(
-                        new SelectableTargetInfo(origTarget, target, targetScore));
-
-                if (isInserted && isShortcutResult) {
-                    mNumShortcutResults++;
-                }
-
-                shouldNotify |= isInserted;
-
-                if (DEBUG) {
-                    Log.d(TAG, " => " + target.toString() + " score=" + targetScore
-                            + " base=" + target.getScore()
-                            + " lastScore=" + lastScore
-                            + " baseScore=" + baseScore);
-                }
-
-                lastScore = targetScore;
-            }
-
-            if (shouldNotify) {
-                notifyDataSetChanged();
-            }
-        }
-
-        private int getNumShortcutResults() {
-            return mNumShortcutResults;
-        }
-
-        /**
-          * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
-          * <ol>
-          *   <li>App-supplied targets
-          *   <li>Shortcuts ranked via App Prediction Manager
-          *   <li>Shortcuts ranked via legacy heuristics
-          *   <li>Legacy direct share targets
-          * </ol>
-          */
-        public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
-            if (target == null) {
-                return CALLER_TARGET_SCORE_BOOST;
-            }
-
-            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
-                return SHORTCUT_TARGET_SCORE_BOOST;
-            }
-
-            float score = super.getScore(target);
-            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
-                return score * SHORTCUT_TARGET_SCORE_BOOST;
-            }
-
-            return score;
-        }
-
-        /**
-         * Calling this marks service target loading complete, and will attempt to no longer
-         * update the direct share area.
-         */
-        public void completeServiceTargetLoading() {
-            mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
-
-            if (mServiceTargets.isEmpty()) {
-                mServiceTargets.add(new EmptyTargetInfo());
-            }
-            notifyDataSetChanged();
-        }
-
-        private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
-            // Avoid inserting any potentially late results
-            if (mServiceTargets.size() == 1
-                    && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
-                return false;
-            }
-
-            // Check for duplicates and abort if found
-            for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
-                if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
-                    return false;
-                }
-            }
-
-            int currentSize = mServiceTargets.size();
-            final float newScore = chooserTargetInfo.getModifiedScore();
-            for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
-                final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
-                if (serviceTarget == null) {
-                    mServiceTargets.set(i, chooserTargetInfo);
-                    return true;
-                } else if (newScore > serviceTarget.getModifiedScore()) {
-                    mServiceTargets.add(i, chooserTargetInfo);
-                    return true;
-                }
-            }
-
-            if (currentSize < MAX_SERVICE_TARGETS) {
-                mServiceTargets.add(chooserTargetInfo);
-                return true;
-            }
-
-            return false;
-        }
-    }
-
     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
         @Override
         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
@@ -2924,8 +2088,77 @@
         }
     }
 
+    @Override // ResolverListCommunicator
+    public void onHandlePackagesChanged() {
+        mServicesRequested.clear();
+        mAdapter.notifyDataSetChanged();
+        super.onHandlePackagesChanged();
+    }
 
-    private boolean isSendAction(Intent targetIntent) {
+    @Override // SelectableTargetInfoCommunicator
+    public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
+        return mChooserListAdapter.makePresentationGetter(info);
+    }
+
+    @Override // SelectableTargetInfoCommunicator
+    public Intent getReferrerFillInIntent() {
+        return mReferrerFillInIntent;
+    }
+
+    @Override // ChooserListCommunicator
+    public int getMaxRankedTargets() {
+        return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
+    }
+
+    @Override // ChooserListCommunicator
+    public void sendListViewUpdateMessage() {
+        mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
+                LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+    }
+
+    @Override
+    public void onListRebuilt() {
+        if (mChooserListAdapter.mDisplayList == null
+                || mChooserListAdapter.mDisplayList.isEmpty()) {
+            mChooserListAdapter.notifyDataSetChanged();
+        } else {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... voids) {
+                    mChooserListAdapter.updateAlphabeticalList();
+                    return null;
+                }
+                @Override
+                protected void onPostExecute(Void aVoid) {
+                    mChooserListAdapter.notifyDataSetChanged();
+                }
+            }.execute();
+        }
+
+        // don't support direct share on low ram devices
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            return;
+        }
+
+        if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+                || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            if (DEBUG) {
+                Log.d(TAG, "querying direct share targets from ShortcutManager");
+            }
+
+            queryDirectShareTargets(mChooserListAdapter, false);
+        }
+        if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+            if (DEBUG) {
+                Log.d(TAG, "List built querying services");
+            }
+
+            queryTargetServices(mChooserListAdapter);
+        }
+    }
+
+    @Override // ChooserListCommunicator
+    public boolean isSendAction(Intent targetIntent) {
         if (targetIntent == null) {
             return false;
         }
@@ -3080,7 +2313,8 @@
         // There can be at most one row in the listview, that is internally
         // a ViewGroup with 2 rows
         public int getServiceTargetRowCount() {
-            if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
+            if (isSendAction(getTargetIntent())
+                    && !ActivityManager.isLowRamDeviceStatic()) {
                 return 1;
             }
             return 0;
@@ -3177,7 +2411,7 @@
                     getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
             mProfileView = profileRow.findViewById(R.id.profile_button);
             mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
-            bindProfileView();
+            updateProfileViewButton();
             return profileRow;
         }
 
diff --git a/core/java/com/android/internal/app/ChooserFlags.java b/core/java/com/android/internal/app/ChooserFlags.java
new file mode 100644
index 0000000..f1f1dbf
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserFlags.java
@@ -0,0 +1,38 @@
+/*
+ * 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.app;
+
+import android.app.prediction.AppPredictionManager;
+
+/**
+ * Common flags for {@link ChooserListAdapter} and {@link ChooserActivity}.
+ */
+public class ChooserFlags {
+    /**
+     * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
+     * binding to every ChooserTargetService implementation.
+     */
+    // TODO(b/121287573): Replace with a system flag (setprop?)
+    public static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+
+    /**
+     * If {@link ChooserFlags#USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+     * {@link AppPredictionManager} will be queried for direct share targets.
+     */
+    // TODO(b/123089490): Replace with system flag
+    static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
+}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
new file mode 100644
index 0000000..6eb470f
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -0,0 +1,539 @@
+/*
+ * 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.app;
+
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.UserManager;
+import android.service.chooser.ChooserTarget;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.ChooserTargetInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ChooserListAdapter extends ResolverListAdapter {
+    private static final String TAG = "ChooserListAdapter";
+    private static final boolean DEBUG = false;
+
+    public static final int TARGET_BAD = -1;
+    public static final int TARGET_CALLER = 0;
+    public static final int TARGET_SERVICE = 1;
+    public static final int TARGET_STANDARD = 2;
+    public static final int TARGET_STANDARD_AZ = 3;
+
+    private static final int MAX_SUGGESTED_APP_TARGETS = 4;
+    private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+
+    static final int MAX_SERVICE_TARGETS = 8;
+
+    /** {@link #getBaseScore} */
+    public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
+    /** {@link #getBaseScore} */
+    public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
+
+    private final int mMaxShortcutTargetsPerApp;
+    private final ChooserListCommunicator mChooserListCommunicator;
+    private final SelectableTargetInfo.SelectableTargetInfoCommunicator
+            mSelectableTargetInfoComunicator;
+
+    private int mNumShortcutResults = 0;
+
+    // Reserve spots for incoming direct share targets by adding placeholders
+    private ChooserTargetInfo
+            mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
+    private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
+    private final List<TargetInfo> mCallerTargets = new ArrayList<>();
+
+    private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
+            new ChooserActivity.BaseChooserTargetComparator();
+    private boolean mListViewDataChanged = false;
+
+    // Sorted list of DisplayResolveInfos for the alphabetical app section.
+    private List<DisplayResolveInfo> mSortedList = new ArrayList<>();
+
+    public ChooserListAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed, ResolverListController resolverListController,
+            boolean useLayoutForBrowsables,
+            ChooserListCommunicator chooserListCommunicator,
+            SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoComunicator) {
+        // Don't send the initial intents through the shared ResolverActivity path,
+        // we want to separate them into a different section.
+        super(context, payloadIntents, null, rList, filterLastUsed,
+                resolverListController, useLayoutForBrowsables,
+                chooserListCommunicator);
+
+        createPlaceHolders();
+        mMaxShortcutTargetsPerApp =
+                context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
+        mChooserListCommunicator = chooserListCommunicator;
+        mSelectableTargetInfoComunicator = selectableTargetInfoComunicator;
+
+        if (initialIntents != null) {
+            final PackageManager pm = context.getPackageManager();
+            for (int i = 0; i < initialIntents.length; i++) {
+                final Intent ii = initialIntents[i];
+                if (ii == null) {
+                    continue;
+                }
+
+                // We reimplement Intent#resolveActivityInfo here because if we have an
+                // implicit intent, we want the ResolveInfo returned by PackageManager
+                // instead of one we reconstruct ourselves. The ResolveInfo returned might
+                // have extra metadata and resolvePackageName set and we want to respect that.
+                ResolveInfo ri = null;
+                ActivityInfo ai = null;
+                final ComponentName cn = ii.getComponent();
+                if (cn != null) {
+                    try {
+                        ai = pm.getActivityInfo(ii.getComponent(), 0);
+                        ri = new ResolveInfo();
+                        ri.activityInfo = ai;
+                    } catch (PackageManager.NameNotFoundException ignored) {
+                        // ai will == null below
+                    }
+                }
+                if (ai == null) {
+                    ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
+                    ai = ri != null ? ri.activityInfo : null;
+                }
+                if (ai == null) {
+                    Log.w(TAG, "No activity found for " + ii);
+                    continue;
+                }
+                UserManager userManager =
+                        (UserManager) context.getSystemService(Context.USER_SERVICE);
+                if (ii instanceof LabeledIntent) {
+                    LabeledIntent li = (LabeledIntent) ii;
+                    ri.resolvePackageName = li.getSourcePackage();
+                    ri.labelRes = li.getLabelResource();
+                    ri.nonLocalizedLabel = li.getNonLocalizedLabel();
+                    ri.icon = li.getIconResource();
+                    ri.iconResourceId = ri.icon;
+                }
+                if (userManager.isManagedProfile()) {
+                    ri.noResourceId = true;
+                    ri.icon = 0;
+                }
+                mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
+            }
+        }
+    }
+
+    @Override
+    public void handlePackagesChanged() {
+        if (DEBUG) {
+            Log.d(TAG, "clearing queryTargets on package change");
+        }
+        createPlaceHolders();
+        mChooserListCommunicator.onHandlePackagesChanged();
+
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        if (!mListViewDataChanged) {
+            mChooserListCommunicator.sendListViewUpdateMessage();
+            mListViewDataChanged = true;
+        }
+    }
+
+    void refreshListView() {
+        if (mListViewDataChanged) {
+            super.notifyDataSetChanged();
+        }
+        mListViewDataChanged = false;
+    }
+
+
+    private void createPlaceHolders() {
+        mNumShortcutResults = 0;
+        mServiceTargets.clear();
+        for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
+            mServiceTargets.add(mPlaceHolderTargetInfo);
+        }
+    }
+
+    @Override
+    public View onCreateView(ViewGroup parent) {
+        return mInflater.inflate(
+                com.android.internal.R.layout.resolve_grid_item, parent, false);
+    }
+
+    @Override
+    protected void onBindView(View view, TargetInfo info) {
+        super.onBindView(view, info);
+
+        // If target is loading, show a special placeholder shape in the label, make unclickable
+        final ViewHolder holder = (ViewHolder) view.getTag();
+        if (info instanceof ChooserActivity.PlaceHolderTargetInfo) {
+            final int maxWidth = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.chooser_direct_share_label_placeholder_max_width);
+            holder.text.setMaxWidth(maxWidth);
+            holder.text.setBackground(mContext.getResources().getDrawable(
+                    R.drawable.chooser_direct_share_label_placeholder, mContext.getTheme()));
+            // Prevent rippling by removing background containing ripple
+            holder.itemView.setBackground(null);
+        } else {
+            holder.text.setMaxWidth(Integer.MAX_VALUE);
+            holder.text.setBackground(null);
+            holder.itemView.setBackground(holder.defaultItemViewBackground);
+        }
+    }
+
+    void updateAlphabeticalList() {
+        mSortedList.clear();
+        mSortedList.addAll(mDisplayList);
+        Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
+    }
+
+    @Override
+    public boolean shouldGetResolvedFilter() {
+        return true;
+    }
+
+    @Override
+    public int getCount() {
+        return getRankedTargetCount() + getAlphaTargetCount()
+                + getSelectableServiceTargetCount() + getCallerTargetCount();
+    }
+
+    @Override
+    public int getUnfilteredCount() {
+        int appTargets = super.getUnfilteredCount();
+        if (appTargets > mChooserListCommunicator.getMaxRankedTargets()) {
+            appTargets = appTargets + mChooserListCommunicator.getMaxRankedTargets();
+        }
+        return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
+    }
+
+
+    public int getCallerTargetCount() {
+        return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
+    }
+
+    /**
+     * Filter out placeholders and non-selectable service targets
+     */
+    public int getSelectableServiceTargetCount() {
+        int count = 0;
+        for (ChooserTargetInfo info : mServiceTargets) {
+            if (info instanceof SelectableTargetInfo) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public int getServiceTargetCount() {
+        if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
+                && !ActivityManager.isLowRamDeviceStatic()) {
+            return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
+        }
+
+        return 0;
+    }
+
+    int getAlphaTargetCount() {
+        int standardCount = super.getCount();
+        return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
+    }
+
+    int getRankedTargetCount() {
+        int spacesAvailable =
+                mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
+        return Math.min(spacesAvailable, super.getCount());
+    }
+
+    public int getPositionTargetType(int position) {
+        int offset = 0;
+
+        final int serviceTargetCount = getServiceTargetCount();
+        if (position < serviceTargetCount) {
+            return TARGET_SERVICE;
+        }
+        offset += serviceTargetCount;
+
+        final int callerTargetCount = getCallerTargetCount();
+        if (position - offset < callerTargetCount) {
+            return TARGET_CALLER;
+        }
+        offset += callerTargetCount;
+
+        final int rankedTargetCount = getRankedTargetCount();
+        if (position - offset < rankedTargetCount) {
+            return TARGET_STANDARD;
+        }
+        offset += rankedTargetCount;
+
+        final int standardTargetCount = getAlphaTargetCount();
+        if (position - offset < standardTargetCount) {
+            return TARGET_STANDARD_AZ;
+        }
+
+        return TARGET_BAD;
+    }
+
+    @Override
+    public TargetInfo getItem(int position) {
+        return targetInfoForPosition(position, true);
+    }
+
+
+    /**
+     * Find target info for a given position.
+     * Since ChooserActivity displays several sections of content, determine which
+     * section provides this item.
+     */
+    @Override
+    public TargetInfo targetInfoForPosition(int position, boolean filtered) {
+        int offset = 0;
+
+        // Direct share targets
+        final int serviceTargetCount = filtered ? getServiceTargetCount() :
+                getSelectableServiceTargetCount();
+        if (position < serviceTargetCount) {
+            return mServiceTargets.get(position);
+        }
+        offset += serviceTargetCount;
+
+        // Targets provided by calling app
+        final int callerTargetCount = getCallerTargetCount();
+        if (position - offset < callerTargetCount) {
+            return mCallerTargets.get(position - offset);
+        }
+        offset += callerTargetCount;
+
+        // Ranked standard app targets
+        final int rankedTargetCount = getRankedTargetCount();
+        if (position - offset < rankedTargetCount) {
+            return filtered ? super.getItem(position - offset)
+                    : getDisplayResolveInfo(position - offset);
+        }
+        offset += rankedTargetCount;
+
+        // Alphabetical complete app target list.
+        if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
+            return mSortedList.get(position - offset);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Evaluate targets for inclusion in the direct share area. May not be included
+     * if score is too low.
+     */
+    public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
+            @ChooserActivity.ShareTargetType int targetType) {
+        if (DEBUG) {
+            Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
+                    + " targets");
+        }
+
+        if (targets.size() == 0) {
+            return;
+        }
+
+        final float baseScore = getBaseScore(origTarget, targetType);
+        Collections.sort(targets, mBaseTargetComparator);
+
+        final boolean isShortcutResult =
+                (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+                        || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+        final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
+                : MAX_CHOOSER_TARGETS_PER_APP;
+        float lastScore = 0;
+        boolean shouldNotify = false;
+        for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
+            final ChooserTarget target = targets.get(i);
+            float targetScore = target.getScore();
+            targetScore *= baseScore;
+            if (i > 0 && targetScore >= lastScore) {
+                // Apply a decay so that the top app can't crowd out everything else.
+                // This incents ChooserTargetServices to define what's truly better.
+                targetScore = lastScore * 0.95f;
+            }
+            boolean isInserted = insertServiceTarget(new SelectableTargetInfo(
+                    mContext, origTarget, target, targetScore, mSelectableTargetInfoComunicator));
+
+            if (isInserted && isShortcutResult) {
+                mNumShortcutResults++;
+            }
+
+            shouldNotify |= isInserted;
+
+            if (DEBUG) {
+                Log.d(TAG, " => " + target.toString() + " score=" + targetScore
+                        + " base=" + target.getScore()
+                        + " lastScore=" + lastScore
+                        + " baseScore=" + baseScore);
+            }
+
+            lastScore = targetScore;
+        }
+
+        if (shouldNotify) {
+            notifyDataSetChanged();
+        }
+    }
+
+    int getNumShortcutResults() {
+        return mNumShortcutResults;
+    }
+
+    /**
+     * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
+     * <ol>
+     *   <li>App-supplied targets
+     *   <li>Shortcuts ranked via App Prediction Manager
+     *   <li>Shortcuts ranked via legacy heuristics
+     *   <li>Legacy direct share targets
+     * </ol>
+     */
+    public float getBaseScore(
+            DisplayResolveInfo target,
+            @ChooserActivity.ShareTargetType int targetType) {
+        if (target == null) {
+            return CALLER_TARGET_SCORE_BOOST;
+        }
+
+        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
+            return SHORTCUT_TARGET_SCORE_BOOST;
+        }
+
+        float score = super.getScore(target);
+        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+            return score * SHORTCUT_TARGET_SCORE_BOOST;
+        }
+
+        return score;
+    }
+
+    /**
+     * Calling this marks service target loading complete, and will attempt to no longer
+     * update the direct share area.
+     */
+    public void completeServiceTargetLoading() {
+        mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
+
+        if (mServiceTargets.isEmpty()) {
+            mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
+        }
+        notifyDataSetChanged();
+    }
+
+    private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
+        // Avoid inserting any potentially late results
+        if (mServiceTargets.size() == 1
+                && mServiceTargets.get(0) instanceof ChooserActivity.EmptyTargetInfo) {
+            return false;
+        }
+
+        // Check for duplicates and abort if found
+        for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
+            if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+                return false;
+            }
+        }
+
+        int currentSize = mServiceTargets.size();
+        final float newScore = chooserTargetInfo.getModifiedScore();
+        for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
+            final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
+            if (serviceTarget == null) {
+                mServiceTargets.set(i, chooserTargetInfo);
+                return true;
+            } else if (newScore > serviceTarget.getModifiedScore()) {
+                mServiceTargets.add(i, chooserTargetInfo);
+                return true;
+            }
+        }
+
+        if (currentSize < MAX_SERVICE_TARGETS) {
+            mServiceTargets.add(chooserTargetInfo);
+            return true;
+        }
+
+        return false;
+    }
+
+    public ChooserTarget getChooserTargetForValue(int value) {
+        return mServiceTargets.get(value).getChooserTarget();
+    }
+
+    /**
+     * Rather than fully sorting the input list, this sorting task will put the top k elements
+     * in the head of input list and fill the tail with other elements in undetermined order.
+     */
+    @Override
+    AsyncTask<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>> createSortingTask() {
+        return new AsyncTask<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>>() {
+            @Override
+            protected List<ResolvedComponentInfo> doInBackground(
+                    List<ResolvedComponentInfo>... params) {
+                mResolverListController.topK(params[0],
+                        mChooserListCommunicator.getMaxRankedTargets());
+                return params[0];
+            }
+            @Override
+            protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
+                processSortedList(sortedComponents);
+                mChooserListCommunicator.updateProfileViewButton();
+                notifyDataSetChanged();
+            }
+        };
+    }
+
+    /**
+     * Necessary methods to communicate between {@link ChooserListAdapter}
+     * and {@link ChooserActivity}.
+     */
+    interface ChooserListCommunicator extends ResolverListCommunicator {
+
+        int getMaxRankedTargets();
+
+        void sendListViewUpdateMessage();
+
+        boolean isSendAction(Intent targetIntent);
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 74996e9..c1c6ac9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -23,7 +23,6 @@
 import android.annotation.UiThread;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.ActivityThread;
 import android.app.VoiceInteractor.PickOptionRequest;
@@ -35,26 +34,18 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LabeledIntent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Insets;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PatternMatcher;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.UserHandle;
@@ -71,7 +62,6 @@
 import android.view.WindowInsets;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
-import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.ListView;
@@ -81,6 +71,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -99,32 +91,33 @@
  * which to go to.  It is not normally used directly by application developers.
  */
 @UiThread
-public class ResolverActivity extends Activity {
-
-    // Temporary flag for new chooser delegate behavior.
-    boolean mEnableChooserDelegate = true;
+public class ResolverActivity extends Activity implements
+        ResolverListAdapter.ResolverListCommunicator {
 
     @UnsupportedAppUsage
-    protected ResolveListAdapter mAdapter;
+    protected ResolverListAdapter mAdapter;
     private boolean mSafeForwardingMode;
     protected AbsListView mAdapterView;
     private Button mAlwaysButton;
     private Button mOnceButton;
     protected View mProfileView;
-    private int mIconDpi;
     private int mLastSelected = AbsListView.INVALID_POSITION;
     private boolean mResolvingHome = false;
     private int mProfileSwitchMessageId = -1;
     private int mLayoutId;
-    private final ArrayList<Intent> mIntents = new ArrayList<>();
+    @VisibleForTesting
+    protected final ArrayList<Intent> mIntents = new ArrayList<>();
     private PickTargetOptionRequest mPickOptionRequest;
     private String mReferrerPackage;
     private CharSequence mTitle;
     private int mDefaultTitleResId;
-    private boolean mUseLayoutForBrowsables;
+
+    @VisibleForTesting
+    protected boolean mUseLayoutForBrowsables;
 
     // Whether or not this activity supports choosing a default handler for the intent.
-    private boolean mSupportsAlwaysUseOption;
+    @VisibleForTesting
+    protected boolean mSupportsAlwaysUseOption;
     protected ResolverDrawerLayout mResolverDrawerLayout;
     @UnsupportedAppUsage
     protected PackageManager mPm;
@@ -132,12 +125,9 @@
 
     private static final String TAG = "ResolverActivity";
     private static final boolean DEBUG = false;
-    private Runnable mPostListReadyRunnable;
 
     private boolean mRegistered;
 
-    private ColorMatrixColorFilter mSuspendedMatrixColorFilter;
-
     protected Insets mSystemWindowInsets = null;
     private Space mFooterSpacer = null;
 
@@ -233,7 +223,7 @@
             @Override
             public void onSomePackagesChanged() {
                 mAdapter.handlePackagesChanged();
-                bindProfileView();
+                updateProfileViewButton();
             }
 
             @Override
@@ -316,9 +306,6 @@
         mRegistered = true;
         mReferrerPackage = getReferrerPackageName();
 
-        final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
-        mIconDpi = am.getLauncherLargeIconDensity();
-
         // Add our initial intent as the first item, regardless of what else has already been added.
         mIntents.add(0, new Intent(intent));
         mTitle = title;
@@ -330,7 +317,17 @@
 
         mSupportsAlwaysUseOption = supportsAlwaysUseOption;
 
-        if (configureContentView(mIntents, initialIntents, rList)) {
+        // The last argument of createAdapter is whether to do special handling
+        // of the last used choice to highlight it in the list.  We need to always
+        // turn this off when running under voice interaction, since it results in
+        // a more complicated UI that the current voice interaction flow is not able
+        // to handle.
+        boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction();
+        mAdapter = createAdapter(this, mIntents, initialIntents, rList,
+                filterLastUsed, mUseLayoutForBrowsables);
+        configureContentView();
+
+        if (rebuildList()) {
             return;
         }
 
@@ -356,11 +353,9 @@
         mProfileView = findViewById(R.id.profile_button);
         if (mProfileView != null) {
             mProfileView.setOnClickListener(this::onProfileClick);
-            bindProfileView();
+            updateProfileViewButton();
         }
 
-        initSuspendedColorMatrix();
-
         final Set<String> categories = intent.getCategories();
         MetricsLogger.action(this, mAdapter.hasFilteredItem()
                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -423,25 +418,7 @@
         }
     }
 
-    private void initSuspendedColorMatrix() {
-        int grayValue = 127;
-        float scale = 0.5f; // half bright
-
-        ColorMatrix tempBrightnessMatrix = new ColorMatrix();
-        float[] mat = tempBrightnessMatrix.getArray();
-        mat[0] = scale;
-        mat[6] = scale;
-        mat[12] = scale;
-        mat[4] = grayValue;
-        mat[9] = grayValue;
-        mat[14] = grayValue;
-
-        ColorMatrix matrix = new ColorMatrix();
-        matrix.setSaturation(0.0f);
-        matrix.preConcat(tempBrightnessMatrix);
-        mSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix);
-    }
-
+    @Override // ResolverListCommunicator
     public void sendVoiceChoicesIfNeeded() {
         if (!isVoiceInteraction()) {
             // Clearly not needed.
@@ -476,6 +453,7 @@
         }
     }
 
+    @Override // SelectableTargetInfoCommunicator ResolverListCommunicator
     public Intent getTargetIntent() {
         return mIntents.isEmpty() ? null : mIntents.get(0);
     }
@@ -492,7 +470,8 @@
         return R.layout.resolver_list;
     }
 
-    protected void bindProfileView() {
+    @Override // ResolverListCommunicator
+    public void updateProfileViewButton() {
         if (mProfileView == null) {
             return;
         }
@@ -583,187 +562,6 @@
         }
     }
 
-
-    /**
-     * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application
-     * icon and label over any IntentFilter or Activity icon to increase user understanding, with an
-     * exception for applications that hold the right permission. Always attempts to use available
-     * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
-     * Strings to strip creative formatting.
-     */
-    private abstract static class TargetPresentationGetter {
-        @Nullable abstract Drawable getIconSubstituteInternal();
-        @Nullable abstract String getAppSubLabelInternal();
-
-        private Context mCtx;
-        private final int mIconDpi;
-        private final boolean mHasSubstitutePermission;
-        private final ApplicationInfo mAi;
-
-        protected PackageManager mPm;
-
-        TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
-            mCtx = ctx;
-            mPm = ctx.getPackageManager();
-            mAi = ai;
-            mIconDpi = iconDpi;
-            mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
-                    android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
-                    mAi.packageName);
-        }
-
-        public Drawable getIcon(UserHandle userHandle) {
-            return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
-        }
-
-        public Bitmap getIconBitmap(UserHandle userHandle) {
-            Drawable dr = null;
-            if (mHasSubstitutePermission) {
-                dr = getIconSubstituteInternal();
-            }
-
-            if (dr == null) {
-                try {
-                    if (mAi.icon != 0) {
-                        dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
-                    }
-                } catch (NameNotFoundException ignore) {
-                }
-            }
-
-            // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
-            if (dr == null) {
-                dr = mAi.loadIcon(mPm);
-            }
-
-            SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
-            Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
-            sif.recycle();
-
-            return icon;
-        }
-
-        public String getLabel() {
-            String label = null;
-            // Apps with the substitute permission will always show the sublabel as their label
-            if (mHasSubstitutePermission) {
-                label = getAppSubLabelInternal();
-            }
-
-            if (label == null) {
-                label = (String) mAi.loadLabel(mPm);
-            }
-
-            return label;
-        }
-
-        public String getSubLabel() {
-            // Apps with the substitute permission will never have a sublabel
-            if (mHasSubstitutePermission) return null;
-            return getAppSubLabelInternal();
-        }
-
-        protected String loadLabelFromResource(Resources res, int resId) {
-            return res.getString(resId);
-        }
-
-        @Nullable
-        protected Drawable loadIconFromResource(Resources res, int resId) {
-            return res.getDrawableForDensity(resId, mIconDpi);
-        }
-
-    }
-
-    /**
-     * Loads the icon and label for the provided ResolveInfo.
-     */
-    @VisibleForTesting
-    public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
-        private final ResolveInfo mRi;
-        public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
-            super(ctx, iconDpi, ri.activityInfo);
-            mRi = ri;
-        }
-
-        @Override
-        Drawable getIconSubstituteInternal() {
-            Drawable dr = null;
-            try {
-                // Do not use ResolveInfo#getIconResource() as it defaults to the app
-                if (mRi.resolvePackageName != null && mRi.icon != 0) {
-                    dr = loadIconFromResource(
-                            mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
-                }
-            } catch (NameNotFoundException e) {
-                Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
-                        + "couldn't find resources for package", e);
-            }
-
-            // Fall back to ActivityInfo if no icon is found via ResolveInfo
-            if (dr == null) dr = super.getIconSubstituteInternal();
-
-            return dr;
-        }
-
-        @Override
-        String getAppSubLabelInternal() {
-            // Will default to app name if no intent filter or activity label set, make sure to
-            // check if subLabel matches label before final display
-            return (String) mRi.loadLabel(mPm);
-        }
-    }
-
-    ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
-        return new ResolveInfoPresentationGetter(this, mIconDpi, ri);
-    }
-
-    /**
-     * Loads the icon and label for the provided ActivityInfo.
-     */
-    @VisibleForTesting
-    public static class ActivityInfoPresentationGetter extends TargetPresentationGetter {
-        private final ActivityInfo mActivityInfo;
-        public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
-                ActivityInfo activityInfo) {
-            super(ctx, iconDpi, activityInfo.applicationInfo);
-            mActivityInfo = activityInfo;
-        }
-
-        @Override
-        Drawable getIconSubstituteInternal() {
-            Drawable dr = null;
-            try {
-                // Do not use ActivityInfo#getIconResource() as it defaults to the app
-                if (mActivityInfo.icon != 0) {
-                    dr = loadIconFromResource(
-                            mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
-                            mActivityInfo.icon);
-                }
-            } catch (NameNotFoundException e) {
-                Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
-                        + "couldn't find resources for package", e);
-            }
-
-            return dr;
-        }
-
-        @Override
-        String getAppSubLabelInternal() {
-            // Will default to app name if no activity label set, make sure to check if subLabel
-            // matches label before final display
-            return (String) mActivityInfo.loadLabel(mPm);
-        }
-    }
-
-    protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
-        return new ActivityInfoPresentationGetter(this, mIconDpi, ai);
-    }
-
-    Drawable loadIconForResolveInfo(ResolveInfo ri) {
-        // Load icons based on the current process. If in work profile icons should be badged.
-        return makePresentationGetter(ri).getIcon(Process.myUserHandle());
-    }
-
     @Override
     protected void onRestart() {
         super.onRestart();
@@ -772,7 +570,7 @@
             mRegistered = true;
         }
         mAdapter.handlePackagesChanged();
-        bindProfileView();
+        updateProfileViewButton();
     }
 
     @Override
@@ -804,12 +602,8 @@
         if (!isChangingConfigurations() && mPickOptionRequest != null) {
             mPickOptionRequest.cancel();
         }
-        if (mPostListReadyRunnable != null) {
-            getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
-            mPostListReadyRunnable = null;
-        }
-        if (mAdapter != null && mAdapter.mResolverListController != null) {
-            mAdapter.mResolverListController.destroy();
+        if (mAdapter != null) {
+            mAdapter.onDestroy();
         }
     }
 
@@ -950,10 +744,30 @@
     /**
      * Replace me in subclasses!
      */
+    @Override // ResolverListCommunicator
     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
         return defIntent;
     }
 
+    @Override // ResolverListCommunicator
+    public void onPostListReady() {
+        setHeader();
+        resetButtonBar();
+        onListRebuilt();
+    }
+
+    protected void onListRebuilt() {
+        int count = mAdapter.getUnfilteredCount();
+        if (count == 1 && mAdapter.getOtherProfile() == null) {
+            // Only one target, so we're a candidate to auto-launch!
+            final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
+            if (shouldAutoLaunchSingleChoice(target)) {
+                safelyStartActivity(target);
+                finish();
+            }
+        }
+    }
+
     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
         final ResolveInfo ri = target.getResolveInfo();
         final Intent intent = target != null ? target.getResolvedIntent() : null;
@@ -1049,8 +863,8 @@
                 // If we don't add back in the component for forwarding the intent to a managed
                 // profile, the preferred activity may not be updated correctly (as the set of
                 // components we tell it we knew about will have changed).
-                final boolean needToAddBackProfileForwardingComponent
-                        = mAdapter.mOtherProfile != null;
+                final boolean needToAddBackProfileForwardingComponent =
+                        mAdapter.getOtherProfile() != null;
                 if (!needToAddBackProfileForwardingComponent) {
                     set = new ComponentName[N];
                 } else {
@@ -1066,8 +880,8 @@
                 }
 
                 if (needToAddBackProfileForwardingComponent) {
-                    set[N] = mAdapter.mOtherProfile.getResolvedComponentName();
-                    final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
+                    set[N] = mAdapter.getOtherProfile().getResolvedComponentName();
+                    final int otherProfileMatch = mAdapter.getOtherProfile().getResolveInfo().match;
                     if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
                 }
 
@@ -1169,7 +983,7 @@
     }
 
 
-    boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+    public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
             int userId) {
         // Pass intent to delegate chooser activity with permission token.
         // TODO: This should move to a trampoline Activity in the system when the ChooserActivity
@@ -1205,6 +1019,7 @@
         // Do nothing
     }
 
+    @Override // ResolverListCommunicator
     public boolean shouldGetActivityMetadata() {
         return false;
     }
@@ -1220,11 +1035,11 @@
         startActivity(in);
     }
 
-    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
-            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-            boolean filterLastUsed) {
-        return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
-                launchedFromUid, filterLastUsed, createListController());
+    public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed, boolean useLayoutForBrowsables) {
+        return new ResolverListAdapter(context, payloadIntents, initialIntents, rList,
+                filterLastUsed, createListController(), useLayoutForBrowsables, this);
     }
 
     @VisibleForTesting
@@ -1238,26 +1053,34 @@
     }
 
     /**
-     * Returns true if the activity is finishing and creation should halt
+     * Sets up the content view.
      */
-    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
-            List<ResolveInfo> rList) {
-        // The last argument of createAdapter is whether to do special handling
-        // of the last used choice to highlight it in the list.  We need to always
-        // turn this off when running under voice interaction, since it results in
-        // a more complicated UI that the current voice interaction flow is not able
-        // to handle.
-        mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
-                mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction());
-        boolean rebuildCompleted = mAdapter.rebuildList();
-
+    private void configureContentView() {
+        if (mAdapter == null) {
+            throw new IllegalStateException("mAdapter cannot be null.");
+        }
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
         } else {
             mLayoutId = getLayoutResource();
         }
         setContentView(mLayoutId);
+        mAdapterView = findViewById(R.id.resolver_list);
+    }
 
+    /**
+     * Returns true if the activity is finishing and creation should halt.
+     * </p>Subclasses must call rebuildListInternal at the end of rebuildList.
+     */
+    protected boolean rebuildList() {
+        return rebuildListInternal();
+    }
+
+    /**
+     * Returns true if the activity is finishing and creation should halt.
+     */
+    final boolean rebuildListInternal() {
+        boolean rebuildCompleted = mAdapter.rebuildList();
         int count = mAdapter.getUnfilteredCount();
 
         // We only rebuild asynchronously when we have multiple elements to sort. In the case where
@@ -1276,10 +1099,7 @@
             }
         }
 
-
-        mAdapterView = findViewById(R.id.resolver_list);
-
-        if (count == 0 && mAdapter.mPlaceholderCount == 0) {
+        if (count == 0 && mAdapter.getPlaceholderCount() == 0) {
             final TextView emptyView = findViewById(R.id.empty);
             emptyView.setVisibility(View.VISIBLE);
             mAdapterView.setVisibility(View.GONE);
@@ -1290,7 +1110,7 @@
         return false;
     }
 
-    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
+    public void onPrepareAdapterView(AbsListView adapterView, ResolverListAdapter adapter) {
         final boolean useHeader = adapter.hasFilteredItem();
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
 
@@ -1317,7 +1137,7 @@
      * Configure the area above the app selection list (title, content preview, etc).
      */
     public void setHeader() {
-        if (mAdapter.getCount() == 0 && mAdapter.mPlaceholderCount == 0) {
+        if (mAdapter.getCount() == 0 && mAdapter.getPlaceholderCount() == 0) {
             final TextView titleView = findViewById(R.id.title);
             if (titleView != null) {
                 titleView.setVisibility(View.GONE);
@@ -1337,9 +1157,8 @@
         }
 
         final ImageView iconView = findViewById(R.id.icon);
-        final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
-        if (iconView != null && iconInfo != null) {
-            new LoadIconTask(iconInfo, iconView).execute();
+        if (iconView != null) {
+            mAdapter.loadFilteredItemIconTaskAsync(iconView);
         }
     }
 
@@ -1382,7 +1201,8 @@
         }
     }
 
-    private boolean useLayoutWithDefault() {
+    @Override // ResolverListCommunicator
+    public boolean useLayoutWithDefault() {
         return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
     }
 
@@ -1397,704 +1217,20 @@
     /**
      * Check a simple match for the component of two ResolveInfos.
      */
-    static boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
+    @Override // ResolverListCommunicator
+    public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
         return lhs == null ? rhs == null
                 : lhs.activityInfo == null ? rhs.activityInfo == null
                 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
     }
 
-    public final class DisplayResolveInfo implements TargetInfo {
-        private final ResolveInfo mResolveInfo;
-        private CharSequence mDisplayLabel;
-        private Drawable mDisplayIcon;
-        private Drawable mBadge;
-        private CharSequence mExtendedInfo;
-        private final Intent mResolvedIntent;
-        private final List<Intent> mSourceIntents = new ArrayList<>();
-        private boolean mIsSuspended;
-
-        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, Intent pOrigIntent) {
-            this(originalIntent, pri, null /*mDisplayLabel*/, null /*mExtendedInfo*/, pOrigIntent);
+    @Override // ResolverListCommunicator
+    public void onHandlePackagesChanged() {
+        if (mAdapter.getCount() == 0) {
+            // We no longer have any items...  just finish the activity.
+            finish();
         }
-
-        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
-                CharSequence pInfo, Intent pOrigIntent) {
-            mSourceIntents.add(originalIntent);
-            mResolveInfo = pri;
-            mDisplayLabel = pLabel;
-            mExtendedInfo = pInfo;
-
-            final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent :
-                    getReplacementIntent(pri.activityInfo, getTargetIntent()));
-            intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
-                    | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-            final ActivityInfo ai = mResolveInfo.activityInfo;
-            intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name));
-
-            mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
-
-            mResolvedIntent = intent;
-        }
-
-        private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) {
-            mSourceIntents.addAll(other.getAllSourceIntents());
-            mResolveInfo = other.mResolveInfo;
-            mDisplayLabel = other.mDisplayLabel;
-            mDisplayIcon = other.mDisplayIcon;
-            mExtendedInfo = other.mExtendedInfo;
-            mResolvedIntent = new Intent(other.mResolvedIntent);
-            mResolvedIntent.fillIn(fillInIntent, flags);
-        }
-
-        public ResolveInfo getResolveInfo() {
-            return mResolveInfo;
-        }
-
-        public CharSequence getDisplayLabel() {
-            if (mDisplayLabel == null) {
-                ResolveInfoPresentationGetter pg = makePresentationGetter(mResolveInfo);
-                mDisplayLabel = pg.getLabel();
-                mExtendedInfo = pg.getSubLabel();
-            }
-            return mDisplayLabel;
-        }
-
-        public boolean hasDisplayLabel() {
-            return mDisplayLabel != null;
-        }
-
-        public void setDisplayLabel(CharSequence displayLabel) {
-            mDisplayLabel = displayLabel;
-        }
-
-        public void setExtendedInfo(CharSequence extendedInfo) {
-            mExtendedInfo = extendedInfo;
-        }
-
-        public Drawable getDisplayIcon() {
-            return mDisplayIcon;
-        }
-
-        @Override
-        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
-            return new DisplayResolveInfo(this, fillInIntent, flags);
-        }
-
-        @Override
-        public List<Intent> getAllSourceIntents() {
-            return mSourceIntents;
-        }
-
-        public void addAlternateSourceIntent(Intent alt) {
-            mSourceIntents.add(alt);
-        }
-
-        public void setDisplayIcon(Drawable icon) {
-            mDisplayIcon = icon;
-        }
-
-        public boolean hasDisplayIcon() {
-            return mDisplayIcon != null;
-        }
-
-        public CharSequence getExtendedInfo() {
-            return mExtendedInfo;
-        }
-
-        public Intent getResolvedIntent() {
-            return mResolvedIntent;
-        }
-
-        @Override
-        public ComponentName getResolvedComponentName() {
-            return new ComponentName(mResolveInfo.activityInfo.packageName,
-                    mResolveInfo.activityInfo.name);
-        }
-
-        @Override
-        public boolean start(Activity activity, Bundle options) {
-            activity.startActivity(mResolvedIntent, options);
-            return true;
-        }
-
-        @Override
-        public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
-            if (mEnableChooserDelegate) {
-                return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
-            } else {
-                activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
-                return true;
-            }
-        }
-
-        @Override
-        public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
-            activity.startActivityAsUser(mResolvedIntent, options, user);
-            return false;
-        }
-
-        public boolean isSuspended() {
-            return mIsSuspended;
-        }
-    }
-
-    List<DisplayResolveInfo> getDisplayList() {
-        return mAdapter.mDisplayList;
-    }
-
-    /**
-     * A single target as represented in the chooser.
-     */
-    public interface TargetInfo {
-        /**
-         * Get the resolved intent that represents this target. Note that this may not be the
-         * intent that will be launched by calling one of the <code>start</code> methods provided;
-         * this is the intent that will be credited with the launch.
-         *
-         * @return the resolved intent for this target
-         */
-        Intent getResolvedIntent();
-
-        /**
-         * Get the resolved component name that represents this target. Note that this may not
-         * be the component that will be directly launched by calling one of the <code>start</code>
-         * methods provided; this is the component that will be credited with the launch.
-         *
-         * @return the resolved ComponentName for this target
-         */
-        ComponentName getResolvedComponentName();
-
-        /**
-         * Start the activity referenced by this target.
-         *
-         * @param activity calling Activity performing the launch
-         * @param options ActivityOptions bundle
-         * @return true if the start completed successfully
-         */
-        boolean start(Activity activity, Bundle options);
-
-        /**
-         * Start the activity referenced by this target as if the ResolverActivity's caller
-         * was performing the start operation.
-         *
-         * @param activity calling Activity (actually) performing the launch
-         * @param options ActivityOptions bundle
-         * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
-         * @return true if the start completed successfully
-         */
-        boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
-
-        /**
-         * Start the activity referenced by this target as a given user.
-         *
-         * @param activity calling activity performing the launch
-         * @param options ActivityOptions bundle
-         * @param user handle for the user to start the activity as
-         * @return true if the start completed successfully
-         */
-        boolean startAsUser(Activity activity, Bundle options, UserHandle user);
-
-        /**
-         * Return the ResolveInfo about how and why this target matched the original query
-         * for available targets.
-         *
-         * @return ResolveInfo representing this target's match
-         */
-        ResolveInfo getResolveInfo();
-
-        /**
-         * Return the human-readable text label for this target.
-         *
-         * @return user-visible target label
-         */
-        CharSequence getDisplayLabel();
-
-        /**
-         * Return any extended info for this target. This may be used to disambiguate
-         * otherwise identical targets.
-         *
-         * @return human-readable disambig string or null if none present
-         */
-        CharSequence getExtendedInfo();
-
-        /**
-         * @return The drawable that should be used to represent this target including badge
-         */
-        Drawable getDisplayIcon();
-
-        /**
-         * Clone this target with the given fill-in information.
-         */
-        TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
-
-        /**
-         * @return the list of supported source intents deduped against this single target
-         */
-        List<Intent> getAllSourceIntents();
-
-        /**
-          * @return true if this target can be selected by the user
-          */
-        boolean isSuspended();
-    }
-
-    public class ResolveListAdapter extends BaseAdapter {
-        private final List<Intent> mIntents;
-        private final Intent[] mInitialIntents;
-        private final List<ResolveInfo> mBaseResolveList;
-        protected ResolveInfo mLastChosen;
-        private DisplayResolveInfo mOtherProfile;
-        ResolverListController mResolverListController;
-        private int mPlaceholderCount;
-        private boolean mAllTargetsAreBrowsers = false;
-
-        protected final LayoutInflater mInflater;
-
-        // This one is the list that the Adapter will actually present.
-        List<DisplayResolveInfo> mDisplayList;
-        List<ResolvedComponentInfo> mUnfilteredResolveList;
-
-        private int mLastChosenPosition = -1;
-        private boolean mFilterLastUsed;
-
-        public ResolveListAdapter(Context context, List<Intent> payloadIntents,
-                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-                boolean filterLastUsed,
-                ResolverListController resolverListController) {
-            mIntents = payloadIntents;
-            mInitialIntents = initialIntents;
-            mBaseResolveList = rList;
-            mLaunchedFromUid = launchedFromUid;
-            mInflater = LayoutInflater.from(context);
-            mDisplayList = new ArrayList<>();
-            mFilterLastUsed = filterLastUsed;
-            mResolverListController = resolverListController;
-        }
-
-        public void handlePackagesChanged() {
-            rebuildList();
-            if (getCount() == 0) {
-                // We no longer have any items...  just finish the activity.
-                finish();
-            }
-        }
-
-        public void setPlaceholderCount(int count) {
-            mPlaceholderCount = count;
-        }
-
-        public int getPlaceholderCount() { return mPlaceholderCount; }
-
-        @Nullable
-        public DisplayResolveInfo getFilteredItem() {
-            if (mFilterLastUsed && mLastChosenPosition >= 0) {
-                // Not using getItem since it offsets to dodge this position for the list
-                return mDisplayList.get(mLastChosenPosition);
-            }
-            return null;
-        }
-
-        public DisplayResolveInfo getOtherProfile() {
-            return mOtherProfile;
-        }
-
-        public int getFilteredPosition() {
-            if (mFilterLastUsed && mLastChosenPosition >= 0) {
-                return mLastChosenPosition;
-            }
-            return AbsListView.INVALID_POSITION;
-        }
-
-        public boolean hasFilteredItem() {
-            return mFilterLastUsed && mLastChosen != null;
-        }
-
-        public float getScore(DisplayResolveInfo target) {
-            return mResolverListController.getScore(target);
-        }
-
-        public void updateModel(ComponentName componentName) {
-            mResolverListController.updateModel(componentName);
-        }
-
-        public void updateChooserCounts(String packageName, int userId, String action) {
-            mResolverListController.updateChooserCounts(packageName, userId, action);
-        }
-
-        /**
-          * @return true if all items in the display list are defined as browsers by
-          *         ResolveInfo.handleAllWebDataURI
-          */
-        public boolean areAllTargetsBrowsers() {
-            return mAllTargetsAreBrowsers;
-        }
-
-        /**
-         * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
-         * to complete.
-         *
-         * @return Whether or not the list building is completed.
-         */
-        protected boolean rebuildList() {
-            List<ResolvedComponentInfo> currentResolveList = null;
-            // Clear the value of mOtherProfile from previous call.
-            mOtherProfile = null;
-            mLastChosen = null;
-            mLastChosenPosition = -1;
-            mAllTargetsAreBrowsers = false;
-            mDisplayList.clear();
-            if (mBaseResolveList != null) {
-                currentResolveList = mUnfilteredResolveList = new ArrayList<>();
-                mResolverListController.addResolveListDedupe(currentResolveList,
-                        getTargetIntent(),
-                        mBaseResolveList);
-            } else {
-                currentResolveList = mUnfilteredResolveList =
-                        mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
-                                shouldGetActivityMetadata(),
-                                mIntents);
-                if (currentResolveList == null) {
-                    processSortedList(currentResolveList);
-                    return true;
-                }
-                List<ResolvedComponentInfo> originalList =
-                        mResolverListController.filterIneligibleActivities(currentResolveList,
-                                true);
-                if (originalList != null) {
-                    mUnfilteredResolveList = originalList;
-                }
-            }
-
-            // So far we only support a single other profile at a time.
-            // The first one we see gets special treatment.
-            for (ResolvedComponentInfo info : currentResolveList) {
-                if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) {
-                    mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
-                            info.getResolveInfoAt(0),
-                            info.getResolveInfoAt(0).loadLabel(mPm),
-                            info.getResolveInfoAt(0).loadLabel(mPm),
-                            getReplacementIntent(info.getResolveInfoAt(0).activityInfo,
-                                    info.getIntentAt(0)));
-                    currentResolveList.remove(info);
-                    break;
-                }
-            }
-
-            if (mOtherProfile == null) {
-                try {
-                    mLastChosen = mResolverListController.getLastChosen();
-                } catch (RemoteException re) {
-                    Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
-                }
-            }
-
-            int N;
-            if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
-                // We only care about fixing the unfilteredList if the current resolve list and
-                // current resolve list are currently the same.
-                List<ResolvedComponentInfo> originalList =
-                        mResolverListController.filterLowPriority(currentResolveList,
-                                mUnfilteredResolveList == currentResolveList);
-                if (originalList != null) {
-                    mUnfilteredResolveList = originalList;
-                }
-
-                if (currentResolveList.size() > 1) {
-                    int placeholderCount = currentResolveList.size();
-                    if (useLayoutWithDefault()) {
-                        --placeholderCount;
-                    }
-                    setPlaceholderCount(placeholderCount);
-                    createSortingTask().execute(currentResolveList);
-                    postListReadyRunnable();
-                    return false;
-                } else {
-                    processSortedList(currentResolveList);
-                    return true;
-                }
-            } else {
-                processSortedList(currentResolveList);
-                return true;
-            }
-        }
-
-        AsyncTask<List<ResolvedComponentInfo>,
-                Void,
-                List<ResolvedComponentInfo>> createSortingTask() {
-            return new AsyncTask<List<ResolvedComponentInfo>,
-                    Void,
-                    List<ResolvedComponentInfo>>() {
-                @Override
-                protected List<ResolvedComponentInfo> doInBackground(
-                        List<ResolvedComponentInfo>... params) {
-                    mResolverListController.sort(params[0]);
-                    return params[0];
-                }
-
-                @Override
-                protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
-                    processSortedList(sortedComponents);
-                    bindProfileView();
-                    notifyDataSetChanged();
-                }
-            };
-        }
-
-        void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
-            int N;
-            if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
-                mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
-
-                // First put the initial items at the top.
-                if (mInitialIntents != null) {
-                    for (int i = 0; i < mInitialIntents.length; i++) {
-                        Intent ii = mInitialIntents[i];
-                        if (ii == null) {
-                            continue;
-                        }
-                        ActivityInfo ai = ii.resolveActivityInfo(
-                                getPackageManager(), 0);
-                        if (ai == null) {
-                            Log.w(TAG, "No activity found for " + ii);
-                            continue;
-                        }
-                        ResolveInfo ri = new ResolveInfo();
-                        ri.activityInfo = ai;
-                        UserManager userManager =
-                                (UserManager) getSystemService(Context.USER_SERVICE);
-                        if (ii instanceof LabeledIntent) {
-                            LabeledIntent li = (LabeledIntent) ii;
-                            ri.resolvePackageName = li.getSourcePackage();
-                            ri.labelRes = li.getLabelResource();
-                            ri.nonLocalizedLabel = li.getNonLocalizedLabel();
-                            ri.icon = li.getIconResource();
-                            ri.iconResourceId = ri.icon;
-                        }
-                        if (userManager.isManagedProfile()) {
-                            ri.noResourceId = true;
-                            ri.icon = 0;
-                        }
-                        mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
-
-                        addResolveInfo(new DisplayResolveInfo(ii, ri,
-                                ri.loadLabel(getPackageManager()), null, ii));
-                    }
-                }
-
-
-                for (ResolvedComponentInfo rci : sortedComponents) {
-                    final ResolveInfo ri = rci.getResolveInfoAt(0);
-                    if (ri != null) {
-                        mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
-                        addResolveInfoWithAlternates(rci);
-                    }
-                }
-            }
-
-            sendVoiceChoicesIfNeeded();
-            postListReadyRunnable();
-        }
-
-
-
-        /**
-         * Some necessary methods for creating the list are initiated in onCreate and will also
-         * determine the layout known. We therefore can't update the UI inline and post to the
-         * handler thread to update after the current task is finished.
-         */
-        private void postListReadyRunnable() {
-            if (mPostListReadyRunnable == null) {
-                mPostListReadyRunnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        setHeader();
-                        resetButtonBar();
-                        onListRebuilt();
-                        mPostListReadyRunnable = null;
-                    }
-                };
-                getMainThreadHandler().post(mPostListReadyRunnable);
-            }
-        }
-
-        public void onListRebuilt() {
-            int count = getUnfilteredCount();
-            if (count == 1 && getOtherProfile() == null) {
-                // Only one target, so we're a candidate to auto-launch!
-                final TargetInfo target = targetInfoForPosition(0, false);
-                if (shouldAutoLaunchSingleChoice(target)) {
-                    safelyStartActivity(target);
-                    finish();
-                }
-            }
-        }
-
-        public boolean shouldGetResolvedFilter() {
-            return mFilterLastUsed;
-        }
-
-        private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
-            final int count = rci.getCount();
-            final Intent intent = rci.getIntentAt(0);
-            final ResolveInfo add = rci.getResolveInfoAt(0);
-            final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
-            final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, replaceIntent);
-            addResolveInfo(dri);
-            if (replaceIntent == intent) {
-                // Only add alternates if we didn't get a specific replacement from
-                // the caller. If we have one it trumps potential alternates.
-                for (int i = 1, N = count; i < N; i++) {
-                    final Intent altIntent = rci.getIntentAt(i);
-                    dri.addAlternateSourceIntent(altIntent);
-                }
-            }
-            updateLastChosenPosition(add);
-        }
-
-        private void updateLastChosenPosition(ResolveInfo info) {
-            // If another profile is present, ignore the last chosen entry.
-            if (mOtherProfile != null) {
-                mLastChosenPosition = -1;
-                return;
-            }
-            if (mLastChosen != null
-                    && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
-                    && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
-                mLastChosenPosition = mDisplayList.size() - 1;
-            }
-        }
-
-        // We assume that at this point we've already filtered out the only intent for a different
-        // targetUserId which we're going to use.
-        private void addResolveInfo(DisplayResolveInfo dri) {
-            if (dri != null && dri.mResolveInfo != null
-                    && dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) {
-                // Checks if this info is already listed in display.
-                for (DisplayResolveInfo existingInfo : mDisplayList) {
-                    if (resolveInfoMatch(dri.mResolveInfo, existingInfo.mResolveInfo)) {
-                        return;
-                    }
-                }
-                mDisplayList.add(dri);
-            }
-        }
-
-        @Nullable
-        public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
-            TargetInfo target = targetInfoForPosition(position, filtered);
-            if (target != null) {
-                return target.getResolveInfo();
-             }
-             return null;
-        }
-
-        @Nullable
-        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
-            if (filtered) {
-                return getItem(position);
-            }
-            if (mDisplayList.size() > position) {
-                return mDisplayList.get(position);
-            }
-            return null;
-        }
-
-        public int getCount() {
-            int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
-                    mDisplayList.size();
-            if (mFilterLastUsed && mLastChosenPosition >= 0) {
-                totalSize--;
-            }
-            return totalSize;
-        }
-
-        public int getUnfilteredCount() {
-            return mDisplayList.size();
-        }
-
-        @Nullable
-        public TargetInfo getItem(int position) {
-            if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
-                position++;
-            }
-            if (mDisplayList.size() > position) {
-                return mDisplayList.get(position);
-            } else {
-                return null;
-            }
-        }
-
-        public long getItemId(int position) {
-            return position;
-        }
-
-        public int getDisplayResolveInfoCount() {
-            return mDisplayList.size();
-        }
-
-        public DisplayResolveInfo getDisplayResolveInfo(int index) {
-            // Used to query services. We only query services for primary targets, not alternates.
-            return mDisplayList.get(index);
-        }
-
-        public final View getView(int position, View convertView, ViewGroup parent) {
-            View view = convertView;
-            if (view == null) {
-                view = createView(parent);
-            }
-            onBindView(view, getItem(position));
-            return view;
-        }
-
-        public final View createView(ViewGroup parent) {
-            final View view = onCreateView(parent);
-            final ViewHolder holder = new ViewHolder(view);
-            view.setTag(holder);
-            return view;
-        }
-
-        public View onCreateView(ViewGroup parent) {
-            return mInflater.inflate(
-                    com.android.internal.R.layout.resolve_list_item, parent, false);
-        }
-
-        public final void bindView(int position, View view) {
-            onBindView(view, getItem(position));
-        }
-
-        protected void onBindView(View view, TargetInfo info) {
-            final ViewHolder holder = (ViewHolder) view.getTag();
-            if (info == null) {
-                holder.icon.setImageDrawable(
-                        getDrawable(R.drawable.resolver_icon_placeholder));
-                return;
-            }
-
-            if (info instanceof DisplayResolveInfo
-                    && !((DisplayResolveInfo) info).hasDisplayLabel()) {
-                getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
-            } else {
-                holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
-            }
-
-            if (info.isSuspended()) {
-                holder.icon.setColorFilter(mSuspendedMatrixColorFilter);
-            } else {
-                holder.icon.setColorFilter(null);
-            }
-
-            if (info instanceof DisplayResolveInfo
-                    && !((DisplayResolveInfo) info).hasDisplayIcon()) {
-                new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
-            } else {
-                holder.icon.setImageDrawable(info.getDisplayIcon());
-            }
-        }
-    }
-
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelTask(info, holder);
     }
 
     @VisibleForTesting
@@ -2144,41 +1280,6 @@
         }
     }
 
-    static class ViewHolder {
-        public View itemView;
-        public Drawable defaultItemViewBackground;
-
-        public TextView text;
-        public TextView text2;
-        public ImageView icon;
-
-        public ViewHolder(View view) {
-            itemView = view;
-            defaultItemViewBackground = view.getBackground();
-            text = (TextView) view.findViewById(com.android.internal.R.id.text1);
-            text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
-            icon = (ImageView) view.findViewById(R.id.icon);
-        }
-
-        public void bindLabel(CharSequence label, CharSequence subLabel) {
-            if (!TextUtils.equals(text.getText(), label)) {
-                text.setText(label);
-            }
-
-            // Always show a subLabel for visual consistency across list items. Show an empty
-            // subLabel if the subLabel is the same as the label
-            if (TextUtils.equals(label, subLabel)) {
-                subLabel = null;
-            }
-
-            if (!TextUtils.equals(text2.getText(), subLabel)
-                    && !TextUtils.isEmpty(subLabel)) {
-                text2.setVisibility(View.VISIBLE);
-                text2.setText(subLabel);
-            }
-        }
-    }
-
     class ItemClickListener implements AdapterView.OnItemClickListener,
             AdapterView.OnItemLongClickListener {
         @Override
@@ -2229,61 +1330,6 @@
 
     }
 
-    protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
-        private final DisplayResolveInfo mDisplayResolveInfo;
-        private final ViewHolder mHolder;
-
-        protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
-            mDisplayResolveInfo = dri;
-            mHolder = holder;
-        }
-
-
-        @Override
-        protected CharSequence[] doInBackground(Void... voids) {
-            ResolveInfoPresentationGetter pg =
-                    makePresentationGetter(mDisplayResolveInfo.mResolveInfo);
-            return new CharSequence[] {
-                    pg.getLabel(),
-                    pg.getSubLabel()
-            };
-        }
-
-        @Override
-        protected void onPostExecute(CharSequence[] result) {
-            mDisplayResolveInfo.setDisplayLabel(result[0]);
-            mDisplayResolveInfo.setExtendedInfo(result[1]);
-            mHolder.bindLabel(result[0], result[1]);
-        }
-    }
-
-    class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
-        protected final DisplayResolveInfo mDisplayResolveInfo;
-        private final ResolveInfo mResolveInfo;
-        private final ImageView mTargetView;
-
-        LoadIconTask(DisplayResolveInfo dri, ImageView target) {
-            mDisplayResolveInfo = dri;
-            mResolveInfo = dri.getResolveInfo();
-            mTargetView = target;
-        }
-
-        @Override
-        protected Drawable doInBackground(Void... params) {
-            return loadIconForResolveInfo(mResolveInfo);
-        }
-
-        @Override
-        protected void onPostExecute(Drawable d) {
-            if (mAdapter.getOtherProfile() == mDisplayResolveInfo) {
-                bindProfileView();
-            } else {
-                mDisplayResolveInfo.setDisplayIcon(d);
-                mTargetView.setImageDrawable(d);
-            }
-        }
-    }
-
     static final boolean isSpecificUriMatch(int match) {
         match = match&IntentFilter.MATCH_CATEGORY_MASK;
         return match >= IntentFilter.MATCH_CATEGORY_HOST
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
new file mode 100644
index 0000000..4076dda
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -0,0 +1,862 @@
+/*
+ * 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.app;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResolverListAdapter extends BaseAdapter {
+    private static final String TAG = "ResolverListAdapter";
+
+    private final List<Intent> mIntents;
+    private final Intent[] mInitialIntents;
+    private final List<ResolveInfo> mBaseResolveList;
+    private final PackageManager mPm;
+    protected final Context mContext;
+    private final ColorMatrixColorFilter mSuspendedMatrixColorFilter;
+    private final boolean mUseLayoutForBrowsables;
+    private final int mIconDpi;
+    protected ResolveInfo mLastChosen;
+    private DisplayResolveInfo mOtherProfile;
+    ResolverListController mResolverListController;
+    private int mPlaceholderCount;
+    private boolean mAllTargetsAreBrowsers = false;
+
+    protected final LayoutInflater mInflater;
+
+    // This one is the list that the Adapter will actually present.
+    List<DisplayResolveInfo> mDisplayList;
+    List<ResolvedComponentInfo> mUnfilteredResolveList;
+
+    private int mLastChosenPosition = -1;
+    private boolean mFilterLastUsed;
+    private final ResolverListCommunicator mResolverListCommunicator;
+    private Runnable mPostListReadyRunnable;
+
+    public ResolverListAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            ResolverListController resolverListController,
+            boolean useLayoutForBrowsables,
+            ResolverListCommunicator resolverListCommunicator) {
+        mContext = context;
+        mIntents = payloadIntents;
+        mInitialIntents = initialIntents;
+        mBaseResolveList = rList;
+        mInflater = LayoutInflater.from(context);
+        mPm = context.getPackageManager();
+        mDisplayList = new ArrayList<>();
+        mFilterLastUsed = filterLastUsed;
+        mResolverListController = resolverListController;
+        mSuspendedMatrixColorFilter = createSuspendedColorMatrix();
+        mUseLayoutForBrowsables = useLayoutForBrowsables;
+        mResolverListCommunicator = resolverListCommunicator;
+        final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
+        mIconDpi = am.getLauncherLargeIconDensity();
+    }
+
+    public void handlePackagesChanged() {
+        rebuildList();
+        mResolverListCommunicator.onHandlePackagesChanged();
+    }
+
+    public void setPlaceholderCount(int count) {
+        mPlaceholderCount = count;
+    }
+
+    public int getPlaceholderCount() {
+        return mPlaceholderCount;
+    }
+
+    @Nullable
+    public DisplayResolveInfo getFilteredItem() {
+        if (mFilterLastUsed && mLastChosenPosition >= 0) {
+            // Not using getItem since it offsets to dodge this position for the list
+            return mDisplayList.get(mLastChosenPosition);
+        }
+        return null;
+    }
+
+    public DisplayResolveInfo getOtherProfile() {
+        return mOtherProfile;
+    }
+
+    public int getFilteredPosition() {
+        if (mFilterLastUsed && mLastChosenPosition >= 0) {
+            return mLastChosenPosition;
+        }
+        return AbsListView.INVALID_POSITION;
+    }
+
+    public boolean hasFilteredItem() {
+        return mFilterLastUsed && mLastChosen != null;
+    }
+
+    public float getScore(DisplayResolveInfo target) {
+        return mResolverListController.getScore(target);
+    }
+
+    public void updateModel(ComponentName componentName) {
+        mResolverListController.updateModel(componentName);
+    }
+
+    public void updateChooserCounts(String packageName, int userId, String action) {
+        mResolverListController.updateChooserCounts(packageName, userId, action);
+    }
+
+    /**
+     * @return true if all items in the display list are defined as browsers by
+     *         ResolveInfo.handleAllWebDataURI
+     */
+    public boolean areAllTargetsBrowsers() {
+        return mAllTargetsAreBrowsers;
+    }
+
+    /**
+     * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
+     * to complete.
+     *
+     * @return Whether or not the list building is completed.
+     */
+    protected boolean rebuildList() {
+        List<ResolvedComponentInfo> currentResolveList = null;
+        // Clear the value of mOtherProfile from previous call.
+        mOtherProfile = null;
+        mLastChosen = null;
+        mLastChosenPosition = -1;
+        mAllTargetsAreBrowsers = false;
+        mDisplayList.clear();
+        if (mBaseResolveList != null) {
+            currentResolveList = mUnfilteredResolveList = new ArrayList<>();
+            mResolverListController.addResolveListDedupe(currentResolveList,
+                    mResolverListCommunicator.getTargetIntent(),
+                    mBaseResolveList);
+        } else {
+            currentResolveList = mUnfilteredResolveList =
+                    mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
+                            mResolverListCommunicator.shouldGetActivityMetadata(),
+                            mIntents);
+            if (currentResolveList == null) {
+                processSortedList(currentResolveList);
+                return true;
+            }
+            List<ResolvedComponentInfo> originalList =
+                    mResolverListController.filterIneligibleActivities(currentResolveList,
+                            true);
+            if (originalList != null) {
+                mUnfilteredResolveList = originalList;
+            }
+        }
+
+        // So far we only support a single other profile at a time.
+        // The first one we see gets special treatment.
+        for (ResolvedComponentInfo info : currentResolveList) {
+            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+                Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
+                        resolveInfo.activityInfo,
+                        info.getIntentAt(0));
+                Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
+                        resolveInfo.activityInfo,
+                        mResolverListCommunicator.getTargetIntent());
+                mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
+                        resolveInfo,
+                        resolveInfo.loadLabel(mPm),
+                        resolveInfo.loadLabel(mPm),
+                        pOrigIntent != null ? pOrigIntent : replacementIntent,
+                        makePresentationGetter(resolveInfo));
+                currentResolveList.remove(info);
+                break;
+            }
+        }
+
+        if (mOtherProfile == null) {
+            try {
+                mLastChosen = mResolverListController.getLastChosen();
+            } catch (RemoteException re) {
+                Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
+            }
+        }
+
+        int n;
+        if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
+            // We only care about fixing the unfilteredList if the current resolve list and
+            // current resolve list are currently the same.
+            List<ResolvedComponentInfo> originalList =
+                    mResolverListController.filterLowPriority(currentResolveList,
+                            mUnfilteredResolveList == currentResolveList);
+            if (originalList != null) {
+                mUnfilteredResolveList = originalList;
+            }
+
+            if (currentResolveList.size() > 1) {
+                int placeholderCount = currentResolveList.size();
+                if (mResolverListCommunicator.useLayoutWithDefault()) {
+                    --placeholderCount;
+                }
+                setPlaceholderCount(placeholderCount);
+                createSortingTask().execute(currentResolveList);
+                postListReadyRunnable();
+                return false;
+            } else {
+                processSortedList(currentResolveList);
+                return true;
+            }
+        } else {
+            processSortedList(currentResolveList);
+            return true;
+        }
+    }
+
+    AsyncTask<List<ResolvedComponentInfo>,
+            Void,
+            List<ResolvedComponentInfo>> createSortingTask() {
+        return new AsyncTask<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>>() {
+            @Override
+            protected List<ResolvedComponentInfo> doInBackground(
+                    List<ResolvedComponentInfo>... params) {
+                mResolverListController.sort(params[0]);
+                return params[0];
+            }
+            @Override
+            protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
+                processSortedList(sortedComponents);
+                mResolverListCommunicator.updateProfileViewButton();
+                notifyDataSetChanged();
+            }
+        };
+    }
+
+
+    protected void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
+        int n;
+        if (sortedComponents != null && (n = sortedComponents.size()) != 0) {
+            mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
+
+            // First put the initial items at the top.
+            if (mInitialIntents != null) {
+                for (int i = 0; i < mInitialIntents.length; i++) {
+                    Intent ii = mInitialIntents[i];
+                    if (ii == null) {
+                        continue;
+                    }
+                    ActivityInfo ai = ii.resolveActivityInfo(
+                            mPm, 0);
+                    if (ai == null) {
+                        Log.w(TAG, "No activity found for " + ii);
+                        continue;
+                    }
+                    ResolveInfo ri = new ResolveInfo();
+                    ri.activityInfo = ai;
+                    UserManager userManager =
+                            (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+                    if (ii instanceof LabeledIntent) {
+                        LabeledIntent li = (LabeledIntent) ii;
+                        ri.resolvePackageName = li.getSourcePackage();
+                        ri.labelRes = li.getLabelResource();
+                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
+                        ri.icon = li.getIconResource();
+                        ri.iconResourceId = ri.icon;
+                    }
+                    if (userManager.isManagedProfile()) {
+                        ri.noResourceId = true;
+                        ri.icon = 0;
+                    }
+                    mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
+
+                    addResolveInfo(new DisplayResolveInfo(ii, ri,
+                            ri.loadLabel(mPm), null, ii, makePresentationGetter(ri)));
+                }
+            }
+
+
+            for (ResolvedComponentInfo rci : sortedComponents) {
+                final ResolveInfo ri = rci.getResolveInfoAt(0);
+                if (ri != null) {
+                    mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
+                    addResolveInfoWithAlternates(rci);
+                }
+            }
+        }
+
+        mResolverListCommunicator.sendVoiceChoicesIfNeeded();
+        postListReadyRunnable();
+    }
+
+    /**
+     * Some necessary methods for creating the list are initiated in onCreate and will also
+     * determine the layout known. We therefore can't update the UI inline and post to the
+     * handler thread to update after the current task is finished.
+     */
+    private void postListReadyRunnable() {
+        if (mPostListReadyRunnable == null) {
+            mPostListReadyRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    mResolverListCommunicator.onPostListReady();
+                    mPostListReadyRunnable = null;
+                }
+            };
+            mContext.getMainThreadHandler().post(mPostListReadyRunnable);
+        }
+    }
+
+    public boolean shouldGetResolvedFilter() {
+        return mFilterLastUsed;
+    }
+
+    private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
+        final int count = rci.getCount();
+        final Intent intent = rci.getIntentAt(0);
+        final ResolveInfo add = rci.getResolveInfoAt(0);
+        final Intent replaceIntent =
+                mResolverListCommunicator.getReplacementIntent(add.activityInfo, intent);
+        final Intent defaultIntent = mResolverListCommunicator.getReplacementIntent(
+                add.activityInfo, mResolverListCommunicator.getTargetIntent());
+        final DisplayResolveInfo
+                dri = new DisplayResolveInfo(intent, add,
+                replaceIntent != null ? replaceIntent : defaultIntent, makePresentationGetter(add));
+        addResolveInfo(dri);
+        if (replaceIntent == intent) {
+            // Only add alternates if we didn't get a specific replacement from
+            // the caller. If we have one it trumps potential alternates.
+            for (int i = 1, n = count; i < n; i++) {
+                final Intent altIntent = rci.getIntentAt(i);
+                dri.addAlternateSourceIntent(altIntent);
+            }
+        }
+        updateLastChosenPosition(add);
+    }
+
+    private void updateLastChosenPosition(ResolveInfo info) {
+        // If another profile is present, ignore the last chosen entry.
+        if (mOtherProfile != null) {
+            mLastChosenPosition = -1;
+            return;
+        }
+        if (mLastChosen != null
+                && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
+                && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
+            mLastChosenPosition = mDisplayList.size() - 1;
+        }
+    }
+
+    // We assume that at this point we've already filtered out the only intent for a different
+    // targetUserId which we're going to use.
+    private void addResolveInfo(DisplayResolveInfo dri) {
+        if (dri != null && dri.getResolveInfo() != null
+                && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
+            // Checks if this info is already listed in display.
+            for (DisplayResolveInfo existingInfo : mDisplayList) {
+                if (mResolverListCommunicator
+                        .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+                    return;
+                }
+            }
+            mDisplayList.add(dri);
+        }
+    }
+
+    @Nullable
+    public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
+        TargetInfo target = targetInfoForPosition(position, filtered);
+        if (target != null) {
+            return target.getResolveInfo();
+        }
+        return null;
+    }
+
+    @Nullable
+    public TargetInfo targetInfoForPosition(int position, boolean filtered) {
+        if (filtered) {
+            return getItem(position);
+        }
+        if (mDisplayList.size() > position) {
+            return mDisplayList.get(position);
+        }
+        return null;
+    }
+
+    public int getCount() {
+        int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
+                mDisplayList.size();
+        if (mFilterLastUsed && mLastChosenPosition >= 0) {
+            totalSize--;
+        }
+        return totalSize;
+    }
+
+    public int getUnfilteredCount() {
+        return mDisplayList.size();
+    }
+
+    @Nullable
+    public TargetInfo getItem(int position) {
+        if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
+            position++;
+        }
+        if (mDisplayList.size() > position) {
+            return mDisplayList.get(position);
+        } else {
+            return null;
+        }
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public int getDisplayResolveInfoCount() {
+        return mDisplayList.size();
+    }
+
+    public DisplayResolveInfo getDisplayResolveInfo(int index) {
+        // Used to query services. We only query services for primary targets, not alternates.
+        return mDisplayList.get(index);
+    }
+
+    public final View getView(int position, View convertView, ViewGroup parent) {
+        View view = convertView;
+        if (view == null) {
+            view = createView(parent);
+        }
+        onBindView(view, getItem(position));
+        return view;
+    }
+
+    public final View createView(ViewGroup parent) {
+        final View view = onCreateView(parent);
+        final ViewHolder holder = new ViewHolder(view);
+        view.setTag(holder);
+        return view;
+    }
+
+    public View onCreateView(ViewGroup parent) {
+        return mInflater.inflate(
+                com.android.internal.R.layout.resolve_list_item, parent, false);
+    }
+
+    public final void bindView(int position, View view) {
+        onBindView(view, getItem(position));
+    }
+
+    protected void onBindView(View view, TargetInfo info) {
+        final ViewHolder holder = (ViewHolder) view.getTag();
+        if (info == null) {
+            holder.icon.setImageDrawable(
+                    mContext.getDrawable(R.drawable.resolver_icon_placeholder));
+            return;
+        }
+
+        if (info instanceof DisplayResolveInfo
+                && !((DisplayResolveInfo) info).hasDisplayLabel()) {
+            getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
+        } else {
+            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
+        }
+
+        if (info.isSuspended()) {
+            holder.icon.setColorFilter(mSuspendedMatrixColorFilter);
+        } else {
+            holder.icon.setColorFilter(null);
+        }
+
+        if (info instanceof DisplayResolveInfo
+                && !((DisplayResolveInfo) info).hasDisplayIcon()) {
+            new ResolverListAdapter.LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
+        } else {
+            holder.icon.setImageDrawable(info.getDisplayIcon(mContext));
+        }
+    }
+
+    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
+        return new LoadLabelTask(info, holder);
+    }
+
+    public void onDestroy() {
+        if (mPostListReadyRunnable != null) {
+            mContext.getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
+            mPostListReadyRunnable = null;
+        }
+        if (mResolverListController != null) {
+            mResolverListController.destroy();
+        }
+    }
+
+    private ColorMatrixColorFilter createSuspendedColorMatrix() {
+        int grayValue = 127;
+        float scale = 0.5f; // half bright
+
+        ColorMatrix tempBrightnessMatrix = new ColorMatrix();
+        float[] mat = tempBrightnessMatrix.getArray();
+        mat[0] = scale;
+        mat[6] = scale;
+        mat[12] = scale;
+        mat[4] = grayValue;
+        mat[9] = grayValue;
+        mat[14] = grayValue;
+
+        ColorMatrix matrix = new ColorMatrix();
+        matrix.setSaturation(0.0f);
+        matrix.preConcat(tempBrightnessMatrix);
+        return new ColorMatrixColorFilter(matrix);
+    }
+
+    ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
+        return new ActivityInfoPresentationGetter(mContext, mIconDpi, ai);
+    }
+
+    ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
+        return new ResolveInfoPresentationGetter(mContext, mIconDpi, ri);
+    }
+
+    Drawable loadIconForResolveInfo(ResolveInfo ri) {
+        // Load icons based on the current process. If in work profile icons should be badged.
+        return makePresentationGetter(ri).getIcon(Process.myUserHandle());
+    }
+
+    void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
+        final DisplayResolveInfo iconInfo = getFilteredItem();
+        if (iconView != null && iconInfo != null) {
+            new LoadIconTask(iconInfo, iconView).execute();
+        }
+    }
+
+    /**
+     * Necessary methods to communicate between {@link ResolverListAdapter}
+     * and {@link ResolverActivity}.
+     */
+    interface ResolverListCommunicator {
+
+        boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs);
+
+        Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
+
+        void onPostListReady();
+
+        void sendVoiceChoicesIfNeeded();
+
+        void updateProfileViewButton();
+
+        boolean useLayoutWithDefault();
+
+        boolean shouldGetActivityMetadata();
+
+        Intent getTargetIntent();
+
+        void onHandlePackagesChanged();
+    }
+
+    static class ViewHolder {
+        public View itemView;
+        public Drawable defaultItemViewBackground;
+
+        public TextView text;
+        public TextView text2;
+        public ImageView icon;
+
+        ViewHolder(View view) {
+            itemView = view;
+            defaultItemViewBackground = view.getBackground();
+            text = (TextView) view.findViewById(com.android.internal.R.id.text1);
+            text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
+            icon = (ImageView) view.findViewById(R.id.icon);
+        }
+
+        public void bindLabel(CharSequence label, CharSequence subLabel) {
+            if (!TextUtils.equals(text.getText(), label)) {
+                text.setText(label);
+            }
+
+            // Always show a subLabel for visual consistency across list items. Show an empty
+            // subLabel if the subLabel is the same as the label
+            if (TextUtils.equals(label, subLabel)) {
+                subLabel = null;
+            }
+
+            if (!TextUtils.equals(text2.getText(), subLabel)
+                    && !TextUtils.isEmpty(subLabel)) {
+                text2.setVisibility(View.VISIBLE);
+                text2.setText(subLabel);
+            }
+        }
+    }
+
+    protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
+        private final DisplayResolveInfo mDisplayResolveInfo;
+        private final ViewHolder mHolder;
+
+        protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
+            mDisplayResolveInfo = dri;
+            mHolder = holder;
+        }
+
+        @Override
+        protected CharSequence[] doInBackground(Void... voids) {
+            ResolveInfoPresentationGetter pg =
+                    makePresentationGetter(mDisplayResolveInfo.getResolveInfo());
+            return new CharSequence[] {
+                    pg.getLabel(),
+                    pg.getSubLabel()
+            };
+        }
+
+        @Override
+        protected void onPostExecute(CharSequence[] result) {
+            mDisplayResolveInfo.setDisplayLabel(result[0]);
+            mDisplayResolveInfo.setExtendedInfo(result[1]);
+            mHolder.bindLabel(result[0], result[1]);
+        }
+    }
+
+    class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
+        protected final com.android.internal.app.chooser.DisplayResolveInfo mDisplayResolveInfo;
+        private final ResolveInfo mResolveInfo;
+        private final ImageView mTargetView;
+
+        LoadIconTask(DisplayResolveInfo dri, ImageView target) {
+            mDisplayResolveInfo = dri;
+            mResolveInfo = dri.getResolveInfo();
+            mTargetView = target;
+        }
+
+        @Override
+        protected Drawable doInBackground(Void... params) {
+            return loadIconForResolveInfo(mResolveInfo);
+        }
+
+        @Override
+        protected void onPostExecute(Drawable d) {
+            if (getOtherProfile() == mDisplayResolveInfo) {
+                mResolverListCommunicator.updateProfileViewButton();
+            } else {
+                mDisplayResolveInfo.setDisplayIcon(d);
+                mTargetView.setImageDrawable(d);
+            }
+        }
+    }
+
+    /**
+     * Loads the icon and label for the provided ResolveInfo.
+     */
+    @VisibleForTesting
+    public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
+        private final ResolveInfo mRi;
+        public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
+            super(ctx, iconDpi, ri.activityInfo);
+            mRi = ri;
+        }
+
+        @Override
+        Drawable getIconSubstituteInternal() {
+            Drawable dr = null;
+            try {
+                // Do not use ResolveInfo#getIconResource() as it defaults to the app
+                if (mRi.resolvePackageName != null && mRi.icon != 0) {
+                    dr = loadIconFromResource(
+                            mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+                        + "couldn't find resources for package", e);
+            }
+
+            // Fall back to ActivityInfo if no icon is found via ResolveInfo
+            if (dr == null) dr = super.getIconSubstituteInternal();
+
+            return dr;
+        }
+
+        @Override
+        String getAppSubLabelInternal() {
+            // Will default to app name if no intent filter or activity label set, make sure to
+            // check if subLabel matches label before final display
+            return (String) mRi.loadLabel(mPm);
+        }
+    }
+
+    /**
+     * Loads the icon and label for the provided ActivityInfo.
+     */
+    @VisibleForTesting
+    public static class ActivityInfoPresentationGetter extends
+            TargetPresentationGetter {
+        private final ActivityInfo mActivityInfo;
+        public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
+                ActivityInfo activityInfo) {
+            super(ctx, iconDpi, activityInfo.applicationInfo);
+            mActivityInfo = activityInfo;
+        }
+
+        @Override
+        Drawable getIconSubstituteInternal() {
+            Drawable dr = null;
+            try {
+                // Do not use ActivityInfo#getIconResource() as it defaults to the app
+                if (mActivityInfo.icon != 0) {
+                    dr = loadIconFromResource(
+                            mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
+                            mActivityInfo.icon);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+                        + "couldn't find resources for package", e);
+            }
+
+            return dr;
+        }
+
+        @Override
+        String getAppSubLabelInternal() {
+            // Will default to app name if no activity label set, make sure to check if subLabel
+            // matches label before final display
+            return (String) mActivityInfo.loadLabel(mPm);
+        }
+    }
+
+    /**
+     * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application
+     * icon and label over any IntentFilter or Activity icon to increase user understanding, with an
+     * exception for applications that hold the right permission. Always attempts to use available
+     * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
+     * Strings to strip creative formatting.
+     */
+    private abstract static class TargetPresentationGetter {
+        @Nullable abstract Drawable getIconSubstituteInternal();
+        @Nullable abstract String getAppSubLabelInternal();
+
+        private Context mCtx;
+        private final int mIconDpi;
+        private final boolean mHasSubstitutePermission;
+        private final ApplicationInfo mAi;
+
+        protected PackageManager mPm;
+
+        TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
+            mCtx = ctx;
+            mPm = ctx.getPackageManager();
+            mAi = ai;
+            mIconDpi = iconDpi;
+            mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
+                    android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
+                    mAi.packageName);
+        }
+
+        public Drawable getIcon(UserHandle userHandle) {
+            return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
+        }
+
+        public Bitmap getIconBitmap(UserHandle userHandle) {
+            Drawable dr = null;
+            if (mHasSubstitutePermission) {
+                dr = getIconSubstituteInternal();
+            }
+
+            if (dr == null) {
+                try {
+                    if (mAi.icon != 0) {
+                        dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
+                    }
+                } catch (PackageManager.NameNotFoundException ignore) {
+                }
+            }
+
+            // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
+            if (dr == null) {
+                dr = mAi.loadIcon(mPm);
+            }
+
+            SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
+            Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
+            sif.recycle();
+
+            return icon;
+        }
+
+        public String getLabel() {
+            String label = null;
+            // Apps with the substitute permission will always show the sublabel as their label
+            if (mHasSubstitutePermission) {
+                label = getAppSubLabelInternal();
+            }
+
+            if (label == null) {
+                label = (String) mAi.loadLabel(mPm);
+            }
+
+            return label;
+        }
+
+        public String getSubLabel() {
+            // Apps with the substitute permission will never have a sublabel
+            if (mHasSubstitutePermission) return null;
+            return getAppSubLabelInternal();
+        }
+
+        protected String loadLabelFromResource(Resources res, int resId) {
+            return res.getString(resId);
+        }
+
+        @Nullable
+        protected Drawable loadIconFromResource(Resources res, int resId) {
+            return res.getDrawableForDensity(resId, mIconDpi);
+        }
+
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 28a8a86..6cc60b7 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.DisplayResolveInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -332,7 +333,7 @@
     }
 
     @VisibleForTesting
-    public float getScore(ResolverActivity.DisplayResolveInfo target) {
+    public float getScore(DisplayResolveInfo target) {
         return mResolverComparator.getScore(target.getResolvedComponentName());
     }
 
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index 7a4e76f..d618cdf 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -214,7 +214,7 @@
      * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
      */
     @Deprecated
-    Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
+    public Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
         // If no icon is provided use the system default
         if (icon == null) {
             icon = getFullResDefaultActivityIcon(mFillResIconDpi);
diff --git a/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java b/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java
new file mode 100644
index 0000000..a2d0953
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java
@@ -0,0 +1,53 @@
+/*
+ * 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.app.chooser;
+
+import android.service.chooser.ChooserTarget;
+import android.text.TextUtils;
+
+/**
+ * A TargetInfo for Direct Share. Includes a {@link ChooserTarget} representing the
+ * Direct Share deep link into an application.
+ */
+public interface ChooserTargetInfo extends TargetInfo {
+    float getModifiedScore();
+
+    ChooserTarget getChooserTarget();
+
+    /**
+     * Do not label as 'equals', since this doesn't quite work
+     * as intended with java 8.
+     */
+    default boolean isSimilar(ChooserTargetInfo other) {
+        if (other == null) return false;
+
+        ChooserTarget ct1 = getChooserTarget();
+        ChooserTarget ct2 = other.getChooserTarget();
+
+        // If either is null, there is not enough info to make an informed decision
+        // about equality, so just exit
+        if (ct1 == null || ct2 == null) return false;
+
+        if (ct1.getComponentName().equals(ct2.getComponentName())
+                && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
+                && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
new file mode 100644
index 0000000..c77444e
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -0,0 +1,182 @@
+/*
+ * 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.app.chooser;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A TargetInfo plus additional information needed to render it (such as icon and label) and
+ * resolve it to an activity.
+ */
+public class DisplayResolveInfo implements TargetInfo {
+    // Temporary flag for new chooser delegate behavior.
+    private static final boolean ENABLE_CHOOSER_DELEGATE = true;
+
+    private final ResolveInfo mResolveInfo;
+    private CharSequence mDisplayLabel;
+    private Drawable mDisplayIcon;
+    private CharSequence mExtendedInfo;
+    private final Intent mResolvedIntent;
+    private final List<Intent> mSourceIntents = new ArrayList<>();
+    private boolean mIsSuspended;
+    private ResolveInfoPresentationGetter mResolveInfoPresentationGetter;
+
+    public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, Intent pOrigIntent,
+            ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+        this(originalIntent, pri, null /*mDisplayLabel*/, null /*mExtendedInfo*/, pOrigIntent,
+                resolveInfoPresentationGetter);
+    }
+
+    public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+            CharSequence pInfo, @NonNull Intent resolvedIntent,
+            @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+        mSourceIntents.add(originalIntent);
+        mResolveInfo = pri;
+        mDisplayLabel = pLabel;
+        mExtendedInfo = pInfo;
+        mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+
+        final Intent intent = new Intent(resolvedIntent);
+        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+                | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+        final ActivityInfo ai = mResolveInfo.activityInfo;
+        intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name));
+
+        mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
+
+        mResolvedIntent = intent;
+    }
+
+    private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags,
+            ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+        mSourceIntents.addAll(other.getAllSourceIntents());
+        mResolveInfo = other.mResolveInfo;
+        mDisplayLabel = other.mDisplayLabel;
+        mDisplayIcon = other.mDisplayIcon;
+        mExtendedInfo = other.mExtendedInfo;
+        mResolvedIntent = new Intent(other.mResolvedIntent);
+        mResolvedIntent.fillIn(fillInIntent, flags);
+        mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+    }
+
+    public ResolveInfo getResolveInfo() {
+        return mResolveInfo;
+    }
+
+    public CharSequence getDisplayLabel() {
+        if (mDisplayLabel == null && mResolveInfoPresentationGetter != null) {
+            mDisplayLabel = mResolveInfoPresentationGetter.getLabel();
+            mExtendedInfo = mResolveInfoPresentationGetter.getSubLabel();
+        }
+        return mDisplayLabel;
+    }
+
+    public boolean hasDisplayLabel() {
+        return mDisplayLabel != null;
+    }
+
+    public void setDisplayLabel(CharSequence displayLabel) {
+        mDisplayLabel = displayLabel;
+    }
+
+    public void setExtendedInfo(CharSequence extendedInfo) {
+        mExtendedInfo = extendedInfo;
+    }
+
+    public Drawable getDisplayIcon(Context context) {
+        return mDisplayIcon;
+    }
+
+    @Override
+    public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+        return new DisplayResolveInfo(this, fillInIntent, flags, mResolveInfoPresentationGetter);
+    }
+
+    @Override
+    public List<Intent> getAllSourceIntents() {
+        return mSourceIntents;
+    }
+
+    public void addAlternateSourceIntent(Intent alt) {
+        mSourceIntents.add(alt);
+    }
+
+    public void setDisplayIcon(Drawable icon) {
+        mDisplayIcon = icon;
+    }
+
+    public boolean hasDisplayIcon() {
+        return mDisplayIcon != null;
+    }
+
+    public CharSequence getExtendedInfo() {
+        return mExtendedInfo;
+    }
+
+    public Intent getResolvedIntent() {
+        return mResolvedIntent;
+    }
+
+    @Override
+    public ComponentName getResolvedComponentName() {
+        return new ComponentName(mResolveInfo.activityInfo.packageName,
+                mResolveInfo.activityInfo.name);
+    }
+
+    @Override
+    public boolean start(Activity activity, Bundle options) {
+        activity.startActivity(mResolvedIntent, options);
+        return true;
+    }
+
+    @Override
+    public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+        if (ENABLE_CHOOSER_DELEGATE) {
+            return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
+        } else {
+            activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
+            return true;
+        }
+    }
+
+    @Override
+    public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+        activity.startActivityAsUser(mResolvedIntent, options, user);
+        return false;
+    }
+
+    public boolean isSuspended() {
+        return mIsSuspended;
+    }
+}
diff --git a/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java
new file mode 100644
index 0000000..22cbdaa6
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java
@@ -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.
+ */
+
+package com.android.internal.app.chooser;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.chooser.ChooserTarget;
+
+import com.android.internal.app.ResolverActivity;
+
+import java.util.List;
+
+/**
+ * Distinguish between targets that selectable by the user, vs those that are
+ * placeholders for the system while information is loading in an async manner.
+ */
+public abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
+
+    public Intent getResolvedIntent() {
+        return null;
+    }
+
+    public ComponentName getResolvedComponentName() {
+        return null;
+    }
+
+    public boolean start(Activity activity, Bundle options) {
+        return false;
+    }
+
+    public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+        return false;
+    }
+
+    public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+        return false;
+    }
+
+    public ResolveInfo getResolveInfo() {
+        return null;
+    }
+
+    public CharSequence getDisplayLabel() {
+        return null;
+    }
+
+    public CharSequence getExtendedInfo() {
+        return null;
+    }
+
+    public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+        return null;
+    }
+
+    public List<Intent> getAllSourceIntents() {
+        return null;
+    }
+
+    public float getModifiedScore() {
+        return -0.1f;
+    }
+
+    public ChooserTarget getChooserTarget() {
+        return null;
+    }
+
+    public boolean isSuspended() {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
new file mode 100644
index 0000000..1cc4857
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -0,0 +1,316 @@
+/*
+ * 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.app.chooser;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.chooser.ChooserTarget;
+import android.text.SpannableStringBuilder;
+import android.util.Log;
+
+import com.android.internal.app.ChooserActivity;
+import com.android.internal.app.ChooserFlags;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.SimpleIconFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Live target, currently selectable by the user.
+ * @see NotSelectableTargetInfo
+ */
+public final class SelectableTargetInfo implements ChooserTargetInfo {
+    private static final String TAG = "SelectableTargetInfo";
+
+    private final Context mContext;
+    private final DisplayResolveInfo mSourceInfo;
+    private final ResolveInfo mBackupResolveInfo;
+    private final ChooserTarget mChooserTarget;
+    private final String mDisplayLabel;
+    private final PackageManager mPm;
+    private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
+    private Drawable mBadgeIcon = null;
+    private CharSequence mBadgeContentDescription;
+    private Drawable mDisplayIcon;
+    private final Intent mFillInIntent;
+    private final int mFillInFlags;
+    private final float mModifiedScore;
+    private boolean mIsSuspended = false;
+
+    public SelectableTargetInfo(Context context, DisplayResolveInfo sourceInfo,
+            ChooserTarget chooserTarget,
+            float modifiedScore, SelectableTargetInfoCommunicator selectableTargetInfoComunicator) {
+        mContext = context;
+        mSourceInfo = sourceInfo;
+        mChooserTarget = chooserTarget;
+        mModifiedScore = modifiedScore;
+        mPm = mContext.getPackageManager();
+        mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+        if (sourceInfo != null) {
+            final ResolveInfo ri = sourceInfo.getResolveInfo();
+            if (ri != null) {
+                final ActivityInfo ai = ri.activityInfo;
+                if (ai != null && ai.applicationInfo != null) {
+                    final PackageManager pm = mContext.getPackageManager();
+                    mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
+                    mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
+                    mIsSuspended =
+                            (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
+                }
+            }
+        }
+        // TODO(b/121287224): do this in the background thread, and only for selected targets
+        mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
+
+        if (sourceInfo != null) {
+            mBackupResolveInfo = null;
+        } else {
+            mBackupResolveInfo =
+                    mContext.getPackageManager().resolveActivity(getResolvedIntent(), 0);
+        }
+
+        mFillInIntent = null;
+        mFillInFlags = 0;
+
+        mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
+    }
+
+    private SelectableTargetInfo(SelectableTargetInfo other,
+            Intent fillInIntent, int flags) {
+        mContext = other.mContext;
+        mPm = other.mPm;
+        mSelectableTargetInfoCommunicator = other.mSelectableTargetInfoCommunicator;
+        mSourceInfo = other.mSourceInfo;
+        mBackupResolveInfo = other.mBackupResolveInfo;
+        mChooserTarget = other.mChooserTarget;
+        mBadgeIcon = other.mBadgeIcon;
+        mBadgeContentDescription = other.mBadgeContentDescription;
+        mDisplayIcon = other.mDisplayIcon;
+        mFillInIntent = fillInIntent;
+        mFillInFlags = flags;
+        mModifiedScore = other.mModifiedScore;
+
+        mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
+    }
+
+    private String sanitizeDisplayLabel(CharSequence label) {
+        SpannableStringBuilder sb = new SpannableStringBuilder(label);
+        sb.clearSpans();
+        return sb.toString();
+    }
+
+    public boolean isSuspended() {
+        return mIsSuspended;
+    }
+
+    /**
+     * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
+     * the call to LauncherApps#getShortcuts(ShortcutQuery).
+     */
+    // TODO(121287224): Refactor code to apply the suggestion above
+    private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
+        Drawable directShareIcon = null;
+
+        // First get the target drawable and associated activity info
+        final Icon icon = target.getIcon();
+        if (icon != null) {
+            directShareIcon = icon.loadDrawable(mContext);
+        } else if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
+            Bundle extras = target.getIntentExtras();
+            if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
+                CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
+                LauncherApps launcherApps = (LauncherApps) mContext.getSystemService(
+                        Context.LAUNCHER_APPS_SERVICE);
+                final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
+                q.setPackage(target.getComponentName().getPackageName());
+                q.setShortcutIds(Arrays.asList(shortcutId.toString()));
+                q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
+                final List<ShortcutInfo> shortcuts =
+                        launcherApps.getShortcuts(q, mContext.getUser());
+                if (shortcuts != null && shortcuts.size() > 0) {
+                    directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
+                }
+            }
+        }
+
+        if (directShareIcon == null) return null;
+
+        ActivityInfo info = null;
+        try {
+            info = mPm.getActivityInfo(target.getComponentName(), 0);
+        } catch (PackageManager.NameNotFoundException error) {
+            Log.e(TAG, "Could not find activity associated with ChooserTarget");
+        }
+
+        if (info == null) return null;
+
+        // Now fetch app icon and raster with no badging even in work profile
+        Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
+                .getIconBitmap(UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+
+        // Raster target drawable with appIcon as a badge
+        SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
+        Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
+        sif.recycle();
+
+        return new BitmapDrawable(mContext.getResources(), directShareBadgedIcon);
+    }
+
+    public float getModifiedScore() {
+        return mModifiedScore;
+    }
+
+    @Override
+    public Intent getResolvedIntent() {
+        if (mSourceInfo != null) {
+            return mSourceInfo.getResolvedIntent();
+        }
+
+        final Intent targetIntent = new Intent(mSelectableTargetInfoCommunicator.getTargetIntent());
+        targetIntent.setComponent(mChooserTarget.getComponentName());
+        targetIntent.putExtras(mChooserTarget.getIntentExtras());
+        return targetIntent;
+    }
+
+    @Override
+    public ComponentName getResolvedComponentName() {
+        if (mSourceInfo != null) {
+            return mSourceInfo.getResolvedComponentName();
+        } else if (mBackupResolveInfo != null) {
+            return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
+                    mBackupResolveInfo.activityInfo.name);
+        }
+        return null;
+    }
+
+    private Intent getBaseIntentToSend() {
+        Intent result = getResolvedIntent();
+        if (result == null) {
+            Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
+        } else {
+            result = new Intent(result);
+            if (mFillInIntent != null) {
+                result.fillIn(mFillInIntent, mFillInFlags);
+            }
+            result.fillIn(mSelectableTargetInfoCommunicator.getReferrerFillInIntent(), 0);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean start(Activity activity, Bundle options) {
+        throw new RuntimeException("ChooserTargets should be started as caller.");
+    }
+
+    @Override
+    public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+        final Intent intent = getBaseIntentToSend();
+        if (intent == null) {
+            return false;
+        }
+        intent.setComponent(mChooserTarget.getComponentName());
+        intent.putExtras(mChooserTarget.getIntentExtras());
+
+        // Important: we will ignore the target security checks in ActivityManager
+        // if and only if the ChooserTarget's target package is the same package
+        // where we got the ChooserTargetService that provided it. This lets a
+        // ChooserTargetService provide a non-exported or permission-guarded target
+        // to the chooser for the user to pick.
+        //
+        // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
+        // so we'll obey the caller's normal security checks.
+        final boolean ignoreTargetSecurity = mSourceInfo != null
+                && mSourceInfo.getResolvedComponentName().getPackageName()
+                .equals(mChooserTarget.getComponentName().getPackageName());
+        return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
+    }
+
+    @Override
+    public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+        throw new RuntimeException("ChooserTargets should be started as caller.");
+    }
+
+    @Override
+    public ResolveInfo getResolveInfo() {
+        return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
+    }
+
+    @Override
+    public CharSequence getDisplayLabel() {
+        return mDisplayLabel;
+    }
+
+    @Override
+    public CharSequence getExtendedInfo() {
+        // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
+        return null;
+    }
+
+    @Override
+    public Drawable getDisplayIcon(Context context) {
+        return mDisplayIcon;
+    }
+
+    public ChooserTarget getChooserTarget() {
+        return mChooserTarget;
+    }
+
+    @Override
+    public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+        return new SelectableTargetInfo(this, fillInIntent, flags);
+    }
+
+    @Override
+    public List<Intent> getAllSourceIntents() {
+        final List<Intent> results = new ArrayList<>();
+        if (mSourceInfo != null) {
+            // We only queried the service for the first one in our sourceinfo.
+            results.add(mSourceInfo.getAllSourceIntents().get(0));
+        }
+        return results;
+    }
+
+    /**
+     * Necessary methods to communicate between {@link SelectableTargetInfo}
+     * and {@link ResolverActivity} or {@link ChooserActivity}.
+     */
+    public interface SelectableTargetInfoCommunicator {
+
+        ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info);
+
+        Intent getTargetIntent();
+
+        Intent getReferrerFillInIntent();
+    }
+}
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
new file mode 100644
index 0000000..b59def1
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -0,0 +1,128 @@
+/*
+ * 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.app.chooser;
+
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+
+import java.util.List;
+
+/**
+ * A single target as represented in the chooser.
+ */
+public interface TargetInfo {
+    /**
+     * Get the resolved intent that represents this target. Note that this may not be the
+     * intent that will be launched by calling one of the <code>start</code> methods provided;
+     * this is the intent that will be credited with the launch.
+     *
+     * @return the resolved intent for this target
+     */
+    Intent getResolvedIntent();
+
+    /**
+     * Get the resolved component name that represents this target. Note that this may not
+     * be the component that will be directly launched by calling one of the <code>start</code>
+     * methods provided; this is the component that will be credited with the launch.
+     *
+     * @return the resolved ComponentName for this target
+     */
+    ComponentName getResolvedComponentName();
+
+    /**
+     * Start the activity referenced by this target.
+     *
+     * @param activity calling Activity performing the launch
+     * @param options ActivityOptions bundle
+     * @return true if the start completed successfully
+     */
+    boolean start(Activity activity, Bundle options);
+
+    /**
+     * Start the activity referenced by this target as if the ResolverActivity's caller
+     * was performing the start operation.
+     *
+     * @param activity calling Activity (actually) performing the launch
+     * @param options ActivityOptions bundle
+     * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
+     * @return true if the start completed successfully
+     */
+    boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
+
+    /**
+     * Start the activity referenced by this target as a given user.
+     *
+     * @param activity calling activity performing the launch
+     * @param options ActivityOptions bundle
+     * @param user handle for the user to start the activity as
+     * @return true if the start completed successfully
+     */
+    boolean startAsUser(Activity activity, Bundle options, UserHandle user);
+
+    /**
+     * Return the ResolveInfo about how and why this target matched the original query
+     * for available targets.
+     *
+     * @return ResolveInfo representing this target's match
+     */
+    ResolveInfo getResolveInfo();
+
+    /**
+     * Return the human-readable text label for this target.
+     *
+     * @return user-visible target label
+     */
+    CharSequence getDisplayLabel();
+
+    /**
+     * Return any extended info for this target. This may be used to disambiguate
+     * otherwise identical targets.
+     *
+     * @return human-readable disambig string or null if none present
+     */
+    CharSequence getExtendedInfo();
+
+    /**
+     * @return The drawable that should be used to represent this target including badge
+     * @param context
+     */
+    Drawable getDisplayIcon(Context context);
+
+    /**
+     * Clone this target with the given fill-in information.
+     */
+    TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+
+    /**
+     * @return the list of supported source intents deduped against this single target
+     */
+    List<Intent> getAllSourceIntents();
+
+    /**
+     * @return true if this target can be selected by the user
+     */
+    boolean isSuspended();
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 025e27b..382a254 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -168,9 +168,6 @@
         if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
             joiner.add("IS_TEXT_EDITOR");
         }
-        if ((startInputFlags & StartInputFlags.FIRST_WINDOW_FOCUS_GAIN) != 0) {
-            joiner.add("FIRST_WINDOW_FOCUS_GAIN");
-        }
         if ((startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0) {
             joiner.add("INITIAL_CONNECTION");
         }
diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java
index ba26d8d..5a8d2c2 100644
--- a/core/java/com/android/internal/inputmethod/StartInputFlags.java
+++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java
@@ -30,7 +30,6 @@
 @IntDef(flag = true, value = {
         StartInputFlags.VIEW_HAS_FOCUS,
         StartInputFlags.IS_TEXT_EDITOR,
-        StartInputFlags.FIRST_WINDOW_FOCUS_GAIN,
         StartInputFlags.INITIAL_CONNECTION})
 public @interface StartInputFlags {
     /**
@@ -44,13 +43,8 @@
     int IS_TEXT_EDITOR = 2;
 
     /**
-     * This is the first time the window has gotten focus.
-     */
-    int FIRST_WINDOW_FOCUS_GAIN = 4;
-
-    /**
      * An internal concept to distinguish "start" and "restart". This concept doesn't look well
      * documented hence we probably need to revisit this though.
      */
-    int INITIAL_CONNECTION = 8;
+    int INITIAL_CONNECTION = 4;
 }
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index bb57805..3704ccd 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -59,6 +59,10 @@
   return instance_;
 }
 
+static bool IsArtMemfd(const std::string& path) {
+  return android::base::StartsWith(path, "/memfd:/boot-image-methods.art");
+}
+
 bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
   // Check the static whitelist path.
   for (const auto& whitelist_path : kPathWhitelist) {
@@ -87,6 +91,11 @@
     return true;
   }
 
+  // the in-memory file created by ART through memfd_create is allowed.
+  if (IsArtMemfd(path)) {
+    return true;
+  }
+
   // Whitelist files needed for Runtime Resource Overlay, like these:
   // /system/vendor/overlay/framework-res.apk
   // /system/vendor/overlay-subdir/pg/framework-res.apk
@@ -312,6 +321,11 @@
     return DetachSocket(fail_fn);
   }
 
+  // Children can directly use the in-memory file created by ART through memfd_create.
+  if (IsArtMemfd(file_path)) {
+    return;
+  }
+
   // NOTE: This might happen if the file was unlinked after being opened.
   // It's a common pattern in the case of temporary files and the like but
   // we should not allow such usage from the zygote.
@@ -531,6 +545,10 @@
 }
 
 void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
+  // ART creates a file through memfd for optimization purposes. We make sure
+  // there is at most one being created.
+  bool art_memfd_seen = false;
+
   // Iterate through the list of file descriptors we've already recorded
   // and check whether :
   //
@@ -563,6 +581,14 @@
         // FD.
       }
 
+      if (IsArtMemfd(it->second->file_path)) {
+        if (art_memfd_seen) {
+          fail_fn("ART fd already seen: " + it->second->file_path);
+        } else {
+          art_memfd_seen = true;
+        }
+      }
+
       ++it;
 
       // Finally, remove the FD from the set of open_fds. We do this last because
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
new file mode 100644
index 0000000..08595bb
--- /dev/null
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.NotificationHistory.HistoricalNotification;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationHistoryTest {
+
+    private HistoricalNotification getHistoricalNotification(int index) {
+        return getHistoricalNotification("package" + index, index);
+    }
+
+    private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+        String expectedChannelName = "channelName" + index;
+        String expectedChannelId = "channelId" + index;
+        int expectedUid = 1123456 + index;
+        int expectedUserId = 11 + index;
+        long expectedPostTime = 987654321 + index;
+        String expectedTitle = "title" + index;
+        String expectedText = "text" + index;
+        Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
+                index);
+
+        return new HistoricalNotification.Builder()
+                .setPackage(packageName)
+                .setChannelName(expectedChannelName)
+                .setChannelId(expectedChannelId)
+                .setUid(expectedUid)
+                .setUserId(expectedUserId)
+                .setPostedTimeMs(expectedPostTime)
+                .setTitle(expectedTitle)
+                .setText(expectedText)
+                .setIcon(expectedIcon)
+                .build();
+    }
+
+    @Test
+    public void testHistoricalNotificationBuilder() {
+        String expectedPackage = "package";
+        String expectedChannelName = "channelName";
+        String expectedChannelId = "channelId";
+        int expectedUid = 1123456;
+        int expectedUserId = 11;
+        long expectedPostTime = 987654321;
+        String expectedTitle = "title";
+        String expectedText = "text";
+        Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
+                android.R.drawable.btn_star);
+
+        HistoricalNotification n = new HistoricalNotification.Builder()
+                .setPackage(expectedPackage)
+                .setChannelName(expectedChannelName)
+                .setChannelId(expectedChannelId)
+                .setUid(expectedUid)
+                .setUserId(expectedUserId)
+                .setPostedTimeMs(expectedPostTime)
+                .setTitle(expectedTitle)
+                .setText(expectedText)
+                .setIcon(expectedIcon)
+                .build();
+
+        assertThat(n.getPackage()).isEqualTo(expectedPackage);
+        assertThat(n.getChannelName()).isEqualTo(expectedChannelName);
+        assertThat(n.getChannelId()).isEqualTo(expectedChannelId);
+        assertThat(n.getUid()).isEqualTo(expectedUid);
+        assertThat(n.getUserId()).isEqualTo(expectedUserId);
+        assertThat(n.getPostedTimeMs()).isEqualTo(expectedPostTime);
+        assertThat(n.getTitle()).isEqualTo(expectedTitle);
+        assertThat(n.getText()).isEqualTo(expectedText);
+        assertThat(expectedIcon.sameAs(n.getIcon())).isTrue();
+    }
+
+    @Test
+    public void testAddNotificationToWrite() {
+        NotificationHistory history = new NotificationHistory();
+        HistoricalNotification n = getHistoricalNotification(0);
+        HistoricalNotification n2 = getHistoricalNotification(1);
+
+        history.addNotificationToWrite(n2);
+        history.addNotificationToWrite(n);
+
+        assertThat(history.getNotificationsToWrite().size()).isEqualTo(2);
+        assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n2);
+        assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
+        assertThat(history.getHistoryCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void testPoolStringsFromNotifications() {
+        NotificationHistory history = new NotificationHistory();
+
+        List<String> expectedStrings = new ArrayList<>();
+        for (int i = 1; i <= 10; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedStrings.add(n.getPackage());
+            expectedStrings.add(n.getChannelName());
+            expectedStrings.add(n.getChannelId());
+            history.addNotificationToWrite(n);
+        }
+
+        history.poolStringsFromNotifications();
+
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(expectedStrings.size());
+        String previous = null;
+        for (String actual : history.getPooledStringsToWrite()) {
+            assertThat(expectedStrings).contains(actual);
+
+            if (previous != null) {
+                assertThat(actual).isGreaterThan(previous);
+            }
+            previous = actual;
+        }
+    }
+
+    @Test
+    public void testAddPooledStrings() {
+        NotificationHistory history = new NotificationHistory();
+
+        List<String> expectedStrings = new ArrayList<>();
+        for (int i = 1; i <= 10; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedStrings.add(n.getPackage());
+            expectedStrings.add(n.getChannelName());
+            expectedStrings.add(n.getChannelId());
+            history.addNotificationToWrite(n);
+        }
+
+        history.addPooledStrings(expectedStrings);
+
+        String[] actualStrings = history.getPooledStringsToWrite();
+        assertThat(actualStrings.length).isEqualTo(expectedStrings.size());
+        String previous = null;
+        for (String actual : actualStrings) {
+            assertThat(expectedStrings).contains(actual);
+
+            if (previous != null) {
+                assertThat(actual).isGreaterThan(previous);
+            }
+            previous = actual;
+        }
+    }
+
+    @Test
+    public void testRemoveNotificationsFromWrite() {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>();
+        List<String> postRemoveExpectedStrings = new ArrayList<>();
+        for (int i = 1; i <= 10; i++) {
+            HistoricalNotification n =
+                    getHistoricalNotification((i % 2 == 0) ? "pkgEven" : "pkgOdd", i);
+
+            if (i % 2 == 0) {
+                postRemoveExpectedStrings.add(n.getPackage());
+                postRemoveExpectedStrings.add(n.getChannelName());
+                postRemoveExpectedStrings.add(n.getChannelId());
+                postRemoveExpectedEntries.add(n);
+            }
+
+            history.addNotificationToWrite(n);
+        }
+
+        history.poolStringsFromNotifications();
+
+        assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
+        // 2 package names and 10 * 2 unique channel names and ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(22);
+
+        history.removeNotificationsFromWrite("pkgOdd");
+
+
+        // 1 package names and 5 * 2 unique channel names and ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(11);
+        assertThat(history.getNotificationsToWrite())
+                .containsExactlyElementsIn(postRemoveExpectedEntries);
+    }
+
+    @Test
+    public void testParceling() {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        for (int i = 10; i >= 1; i--) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedEntries.add(n);
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        Parcel parcel = Parcel.obtain();
+        history.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationHistory parceledHistory = NotificationHistory.CREATOR.createFromParcel(parcel);
+
+        assertThat(parceledHistory.getHistoryCount()).isEqualTo(expectedEntries.size());
+
+        for (int i = 0; i < expectedEntries.size(); i++) {
+            assertThat(parceledHistory.hasNextNotification()).isTrue();
+
+            HistoricalNotification postParcelNotification = parceledHistory.getNextNotification();
+            assertThat(postParcelNotification).isEqualTo(expectedEntries.get(i));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index f14f289..e140ad2 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -82,22 +82,23 @@
 
         final AssetFileDescriptor afd = new AssetFileDescriptor(
                 new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null);
-        when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(afd);
+        when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any(), any())).thenReturn(
+                afd);
     }
 
-    private static void assertImageAspectAndContents(Bitmap bitmap) {
+    private static void assertImageAspectAndContents(int width, int height, Bitmap bitmap) {
         // And correct aspect ratio
-        final int before = (100 * 1280) / 960;
+        final int before = (100 * width) / height;
         final int after = (100 * bitmap.getWidth()) / bitmap.getHeight();
         assertEquals(before, after);
 
         // And scaled correctly
         final int halfX = bitmap.getWidth() / 2;
         final int halfY = bitmap.getHeight() / 2;
-        assertEquals(Color.BLUE, bitmap.getPixel(halfX - 10, halfY - 10));
-        assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY - 10));
-        assertEquals(Color.RED, bitmap.getPixel(halfX - 10, halfY + 10));
-        assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY + 10));
+        assertEquals(Color.BLUE, bitmap.getPixel(halfX - 5, halfY - 5));
+        assertEquals(Color.RED, bitmap.getPixel(halfX + 5, halfY - 5));
+        assertEquals(Color.RED, bitmap.getPixel(halfX - 5, halfY + 5));
+        assertEquals(Color.RED, bitmap.getPixel(halfX + 5, halfY + 5));
     }
 
     @Test
@@ -112,7 +113,7 @@
         assertEquals(1280, res.getWidth());
         assertEquals(960, res.getHeight());
 
-        assertImageAspectAndContents(res);
+        assertImageAspectAndContents(1280, 960, res);
     }
 
     @Test
@@ -127,7 +128,7 @@
         assertTrue(res.getWidth() <= 640);
         assertTrue(res.getHeight() <= 480);
 
-        assertImageAspectAndContents(res);
+        assertImageAspectAndContents(1280, 960, res);
     }
 
     @Test
@@ -142,7 +143,7 @@
         assertTrue(res.getWidth() <= 640);
         assertTrue(res.getHeight() <= 480);
 
-        assertImageAspectAndContents(res);
+        assertImageAspectAndContents(1280, 960, res);
     }
 
     @Test
@@ -157,7 +158,23 @@
         assertEquals(32, res.getWidth());
         assertEquals(24, res.getHeight());
 
-        assertImageAspectAndContents(res);
+        assertImageAspectAndContents(32, 24, res);
+    }
+
+    @Test
+    public void testLoadThumbnail_Large() throws Exception {
+        // Test very large and extreme ratio image
+        initImage(1080, 30000);
+
+        Bitmap res = ContentResolver.loadThumbnail(mClient,
+                Uri.parse("content://com.example/"), new Size(1080, 540), null,
+                ImageDecoder.ALLOCATOR_SOFTWARE);
+
+        // Size should be much smaller
+        assertTrue(res.getWidth() <= 2160);
+        assertTrue(res.getHeight() <= 1080);
+
+        assertImageAspectAndContents(1080, 30000, res);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java b/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java
new file mode 100644
index 0000000..6ae7eb7
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.res.AssetManager;
+import android.graphics.fonts.Font;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TypefaceEqualsTest {
+    @Test
+    public void testFontEqualWithLocale() throws IOException {
+        final AssetManager am =
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+
+        Font masterFont = new Font.Builder(am, "fonts/a3em.ttf").build();
+
+        Font jaFont = new Font.Builder(masterFont.getBuffer(), new File("fonts/a3em.ttf"), "ja")
+                .build();
+        Font jaFont2 = new Font.Builder(masterFont.getBuffer(), new File("fonts/a3em.ttf"), "ja")
+                .build();
+        Font koFont = new Font.Builder(masterFont.getBuffer(), new File("fonts/a3em.ttf"), "ko")
+                .build();
+
+        assertEquals(jaFont, jaFont2);
+        assertNotEquals(jaFont, koFont);
+        assertNotEquals(jaFont, masterFont);
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
index 1bd8ff6..5f640be 100644
--- a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
+++ b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
@@ -93,12 +93,14 @@
     }
 
     @Override
-    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken) {
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+            @Nullable String callingFeatureId, IBinder callerToken) {
         return AppOpsManager.MODE_ALLOWED;
     }
 
     @Override
-    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken) {
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+            @Nullable String callingFeatureId, IBinder callerToken) {
         return AppOpsManager.MODE_ALLOWED;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 5ea91da..d427cbd 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -24,12 +24,12 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import static com.android.internal.app.ChooserActivity.CALLER_TARGET_SCORE_BOOST;
-import static com.android.internal.app.ChooserActivity.SHORTCUT_TARGET_SCORE_BOOST;
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_DEFAULT;
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import static com.android.internal.app.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -69,6 +69,7 @@
 
 import com.android.internal.R;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -819,17 +820,18 @@
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
-        when(sOverrides.resolverListController.getScore(Mockito.isA(
-                ResolverActivity.DisplayResolveInfo.class))).thenReturn(testBaseScore);
+        when(sOverrides.resolverListController.getScore(Mockito.isA(DisplayResolveInfo.class)))
+                .thenReturn(testBaseScore);
 
         final ChooserWrapperActivity activity = mActivityRule
                 .launchActivity(Intent.createChooser(sendIntent, null));
         waitForIdle();
 
-        final ResolverActivity.DisplayResolveInfo testDri =
+        final DisplayResolveInfo testDri =
                 activity.createTestDisplayResolveInfo(sendIntent,
-                ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent);
-        final ChooserActivity.ChooserListAdapter adapter = activity.getAdapter();
+                ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent,
+                /* resolveInfoPresentationGetter */ null);
+        final ChooserListAdapter adapter = activity.getAdapter();
 
         assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
         assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
@@ -970,7 +972,8 @@
                                 ri,
                                 "testLabel",
                                 "testInfo",
-                                sendIntent),
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET)
         );
@@ -1036,7 +1039,8 @@
                                 ri,
                                 "testLabel",
                                 "testInfo",
-                                sendIntent),
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET)
         );
@@ -1097,7 +1101,8 @@
                                 ri,
                                 "testLabel",
                                 "testInfo",
-                                sendIntent),
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET)
         );
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 1d567c7..03705d0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.annotation.Nullable;
 import android.app.usage.UsageStatsManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +31,9 @@
 import android.net.Uri;
 import android.util.Size;
 
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -64,7 +68,7 @@
     }
 
     @Override
-    public void safelyStartActivity(TargetInfo cti) {
+    public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
         if (sOverrides.onSafelyStartCallback != null &&
                 sOverrides.onSafelyStartCallback.apply(cti)) {
             return;
@@ -133,8 +137,10 @@
     }
 
     public DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
-            CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) {
-        return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, pOrigIntent);
+            CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
+            @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+        return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, replacementIntent,
+                resolveInfoPresentationGetter);
     }
 
     /**
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 0fa29bf..a401e21 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -43,10 +43,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
-import com.android.internal.app.ResolverActivity.ActivityInfoPresentationGetter;
-import com.android.internal.app.ResolverActivity.ResolveInfoPresentationGetter;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.ResolverDataProvider.PackageManagerMockedInfo;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
 import com.android.internal.widget.ResolverDrawerLayout;
 
 import org.junit.Before;
@@ -83,7 +83,7 @@
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
-        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
         waitForIdle();
 
         assertThat(activity.getAdapter().getCount(), is(2));
@@ -216,7 +216,7 @@
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
-        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
         waitForIdle();
 
         // The other entry is filtered to the last used slot
@@ -254,7 +254,7 @@
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
-        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
         waitForIdle();
 
         // The other entry is filtered to the other profile slot
@@ -300,7 +300,7 @@
                 .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
-        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
         waitForIdle();
 
         // The other entry is filtered to the other profile slot
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 9082543..39cc83c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -19,10 +19,14 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 
-import androidx.test.espresso.idling.CountingIdlingResource;
+import com.android.internal.app.chooser.TargetInfo;
 
+import java.util.List;
 import java.util.function.Function;
 
 /*
@@ -31,15 +35,17 @@
 public class ResolverWrapperActivity extends ResolverActivity {
     static final OverrideData sOverrides = new OverrideData();
     private UsageStatsManager mUsm;
-    private CountingIdlingResource mLabelIdlingResource =
-            new CountingIdlingResource("LoadLabelTask");
 
-    public CountingIdlingResource getLabelIdlingResource() {
-        return mLabelIdlingResource;
+    @Override
+    public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
+            boolean useLayoutForBrowsables) {
+        return new ResolverWrapperAdapter(context, payloadIntents, initialIntents, rList,
+                filterLastUsed, createListController(), useLayoutForBrowsables, this);
     }
 
-    ResolveListAdapter getAdapter() {
-        return mAdapter;
+    ResolverWrapperAdapter getAdapter() {
+        return (ResolverWrapperAdapter) mAdapter;
     }
 
     @Override
@@ -72,11 +78,6 @@
         return super.getPackageManager();
     }
 
-    @Override
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelWrapperTask(info, holder);
-    }
-
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
@@ -96,22 +97,4 @@
             resolverListController = mock(ResolverListController.class);
         }
     }
-
-    class LoadLabelWrapperTask extends LoadLabelTask {
-
-        protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
-            super(dri, holder);
-        }
-
-        @Override
-        protected void onPreExecute() {
-            mLabelIdlingResource.increment();
-        }
-
-        @Override
-        protected void onPostExecute(CharSequence[] result) {
-            super.onPostExecute(result);
-            mLabelIdlingResource.decrement();
-        }
-    }
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
new file mode 100644
index 0000000..e41df41
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import androidx.test.espresso.idling.CountingIdlingResource;
+
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.List;
+
+public class ResolverWrapperAdapter extends ResolverListAdapter {
+
+    private CountingIdlingResource mLabelIdlingResource =
+            new CountingIdlingResource("LoadLabelTask");
+
+    public ResolverWrapperAdapter(Context context,
+            List<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> rList, boolean filterLastUsed,
+            ResolverListController resolverListController, boolean useLayoutForBrowsables,
+            ResolverListCommunicator resolverListCommunicator) {
+        super(context, payloadIntents, initialIntents, rList, filterLastUsed,
+                resolverListController,
+                useLayoutForBrowsables, resolverListCommunicator);
+    }
+
+    public CountingIdlingResource getLabelIdlingResource() {
+        return mLabelIdlingResource;
+    }
+
+    @Override
+    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
+        return new LoadLabelWrapperTask(info, holder);
+    }
+
+    class LoadLabelWrapperTask extends LoadLabelTask {
+
+        protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
+            super(dri, holder);
+        }
+
+        @Override
+        protected void onPreExecute() {
+            mLabelIdlingResource.increment();
+        }
+
+        @Override
+        protected void onPostExecute(CharSequence[] result) {
+            super.onPostExecute(result);
+            mLabelIdlingResource.decrement();
+        }
+    }
+}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index b3f6fdb..364e4ea 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -46,10 +46,12 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
 import android.view.WindowManagerGlobal;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -70,6 +72,8 @@
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
+@Presubmit
+@FlakyTest(bugId = 143153552)
 public class ActivityThreadClientTest {
 
     @Test
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 552088f..ba96a06 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -519,12 +519,13 @@
         }
         Font f = (Font) o;
         return mFontStyle.equals(f.mFontStyle) && f.mTtcIndex == mTtcIndex
-                && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer);
+                && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer)
+                && Objects.equals(f.mLocaleList, mLocaleList);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer);
+        return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer, mLocaleList);
     }
 
     @Override
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index ec83a19..1eb089d 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -136,7 +136,6 @@
                                const Paint* paint) override;
     virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
 
-    virtual bool drawTextAbsolutePos() const override { return true; }
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
     virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index b98ffca..c138a32 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -95,18 +95,10 @@
 
     void operator()(size_t start, size_t end) {
         auto glyphFunc = [&](uint16_t* text, float* positions) {
-            if (canvas->drawTextAbsolutePos()) {
-                for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
-                    text[textIndex++] = layout.getGlyphId(i);
-                    positions[posIndex++] = x + layout.getX(i);
-                    positions[posIndex++] = y + layout.getY(i);
-                }
-            } else {
-                for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
-                    text[textIndex++] = layout.getGlyphId(i);
-                    positions[posIndex++] = layout.getX(i);
-                    positions[posIndex++] = layout.getY(i);
-                }
+            for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+                text[textIndex++] = layout.getGlyphId(i);
+                positions[posIndex++] = x + layout.getX(i);
+                positions[posIndex++] = y + layout.getY(i);
             }
         };
 
@@ -166,9 +158,6 @@
 
     minikin::MinikinRect bounds;
     layout.getBounds(&bounds);
-    if (!drawTextAbsolutePos()) {
-        bounds.offset(x, y);
-    }
 
     // Set align to left for drawing, as we don't want individual
     // glyphs centered or right-aligned; the offset above takes
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index b90a4a3..27dfed3 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -253,15 +253,6 @@
     virtual void drawPicture(const SkPicture& picture) = 0;
 
     /**
-     * Specifies if the positions passed to ::drawText are absolute or relative
-     * to the (x,y) value provided.
-     *
-     * If true the (x,y) values are ignored. Otherwise, those (x,y) values need
-     * to be added to each glyph's position to get its absolute position.
-     */
-    virtual bool drawTextAbsolutePos() const = 0;
-
-    /**
      * Draws a VectorDrawable onto the canvas.
      */
     virtual void drawVectorDrawable(VectorDrawableRoot* tree) = 0;
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d3db9d8..c3aae7d 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1835,7 +1835,7 @@
         }
 
         try {
-            return mGnssStatusListenerManager.addListener(listener, new Handler());
+            return mGnssStatusListenerManager.addListener(listener, Runnable::run);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2694,9 +2694,9 @@
             return mTtff;
         }
 
-        public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Handler handler)
+        public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor)
                 throws RemoteException {
-            return addInternal(listener, handler);
+            return addInternal(listener, executor);
         }
 
         public boolean addListener(@NonNull OnNmeaMessageListener listener,
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index bf7da23..9723652 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -337,9 +337,14 @@
 
         /**
          * Audio source for capturing broadcast radio tuner output.
+         * Capturing the radio tuner output requires the
+         * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
+         * This permission is reserved for use by system components and is not available to
+         * third-party applications.
          * @hide
          */
         @SystemApi
+        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
         public static final int RADIO_TUNER = 1998;
 
         /**
diff --git a/media/java/android/media/tv/OWNER b/media/java/android/media/tv/OWNER
new file mode 100644
index 0000000..64c0bb5
--- /dev/null
+++ b/media/java/android/media/tv/OWNER
@@ -0,0 +1,5 @@
+amyjojo@google.com
+nchalko@google.com
+shubang@google.com
+quxiangfang@google.com
+
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 0228dc9..2257747 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,6 +16,13 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.IntDef;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
 /**
  * Tuner is used to interact with tuner devices.
  *
@@ -25,11 +32,41 @@
     private static final String TAG = "MediaTvTuner";
     private static final boolean DEBUG = false;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
+            FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
+            FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
+    public @interface FrontendType {}
+
+    public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+    public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
+    public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
+    public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+    public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
+    public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
+    public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
+    public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
+    public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+    public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
+            FRONTEND_EVENT_TYPE_LOST_LOCK})
+    public @interface FrontendEventType {}
+
+    public static final int FRONTEND_EVENT_TYPE_LOCKED = Constants.FrontendEventType.LOCKED;
+    public static final int FRONTEND_EVENT_TYPE_NO_SIGNAL = Constants.FrontendEventType.NO_SIGNAL;
+    public static final int FRONTEND_EVENT_TYPE_LOST_LOCK = Constants.FrontendEventType.LOST_LOCK;
+
     static {
         System.loadLibrary("media_tv_tuner");
         nativeInit();
     }
 
+    private FrontendCallback mFrontendCallback;
+    private List<Integer> mFrontendIds;
+
     public Tuner() {
         nativeSetup();
     }
@@ -48,4 +85,48 @@
      * Native setup.
      */
     private native void nativeSetup();
+
+    /**
+     * Native method to get all frontend IDs.
+     */
+    private native List<Integer> nativeGetFrontendIds();
+
+    /**
+     * Native method to open frontend of the given ID.
+     */
+    private native Frontend nativeOpenFrontendById(int id);
+
+
+    /**
+     * Frontend Callback.
+     */
+    public interface FrontendCallback {
+
+        /**
+         * Invoked when there is a frontend event.
+         */
+        void onEvent(int frontendEventType);
+    }
+
+    protected static class Frontend {
+        int mId;
+        private Frontend(int id) {
+            mId = id;
+        }
+    }
+
+    private List<Integer> getFrontendIds() {
+        mFrontendIds = nativeGetFrontendIds();
+        return mFrontendIds;
+    }
+
+    private Frontend openFrontendById(int id) {
+        if (mFrontendIds == null) {
+            getFrontendIds();
+        }
+        if (!mFrontendIds.contains(id)) {
+            return null;
+        }
+        return nativeOpenFrontendById(id);
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index a596d89..3742f97 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -132,6 +132,7 @@
     shared_libs: [
         "android.hardware.tv.tuner@1.0",
         "libandroid_runtime",
+        "libhidlbase",
         "liblog",
         "libutils",
     ],
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index d499eee..ba133fc 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -25,10 +25,13 @@
 
 #pragma GCC diagnostic ignored "-Wunused-function"
 
+using ::android::hardware::hidl_vec;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
+using ::android::hardware::tv::tuner::V1_0::Result;
 
 struct fields_t {
     jfieldID context;
+    jmethodID frontendInitID;
 };
 
 static fields_t gFields;
@@ -69,6 +72,50 @@
     return mTuner;
 }
 
+jobject JTuner::getFrontendIds() {
+    ALOGD("JTuner::getFrontendIds()");
+    hidl_vec<FrontendId> feIds;
+    mTuner->getFrontendIds([&](Result, const hidl_vec<FrontendId>& frontendIds) {
+        feIds = frontendIds;
+    });
+    if (feIds.size() == 0) {
+        ALOGW("Frontend isn't available");
+        return NULL;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass arrayListClazz = env->FindClass("java/util/ArrayList");
+    jmethodID arrayListAdd = env->GetMethodID(arrayListClazz, "add", "(Ljava/lang/Object;)Z");
+    jobject obj = env->NewObject(arrayListClazz, env->GetMethodID(arrayListClazz, "<init>", "()V"));
+
+    jclass integerClazz = env->FindClass("java/lang/Integer");
+    jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V");
+
+    for (int i=0; i < feIds.size(); i++) {
+       jobject idObj = env->NewObject(integerClazz, intInit, feIds[i]);
+       env->CallBooleanMethod(obj, arrayListAdd, idObj);
+    }
+    return obj;
+}
+
+jobject JTuner::openFrontendById(int id) {
+    mTuner->openFrontendById(id, [&](Result, const sp<IFrontend>& frontend) {
+        mFe = frontend;
+    });
+    if (mFe == nullptr) {
+        ALOGE("Failed to open frontend");
+        return NULL;
+    }
+
+    jint jId = (jint) id;
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    // TODO: add more fields to frontend
+    return env->NewObject(
+            env->FindClass("android/media/tv/tuner/Tuner$Frontend"),
+            gFields.frontendInitID,
+            (jint) jId);
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -99,6 +146,9 @@
 
     gFields.context = env->GetFieldID(clazz, "mNativeContext", "J");
     CHECK(gFields.context != NULL);
+
+    jclass frontendClazz = env->FindClass("android/media/tv/tuner/Tuner$Frontend");
+    gFields.frontendInitID = env->GetMethodID(frontendClazz, "<init>", "(I)V");
 }
 
 static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) {
@@ -106,9 +156,23 @@
     setTuner(env,thiz, tuner);
 }
 
+static jobject android_media_tv_Tuner_get_frontend_ids(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendIds();
+}
+
+static jobject android_media_tv_Tuner_open_frontend_by_id(JNIEnv *env, jobject thiz, jint id) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->openFrontendById(id);
+}
+
 static const JNINativeMethod gMethods[] = {
     { "nativeInit", "()V", (void *)android_media_tv_Tuner_native_init },
     { "nativeSetup", "()V", (void *)android_media_tv_Tuner_native_setup },
+    { "nativeGetFrontendIds", "()Ljava/util/List;",
+            (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 },
 };
 
 static int register_android_media_tv_Tuner(JNIEnv *env) {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index e7d5924..1425542 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -22,6 +22,8 @@
 
 #include "jni.h"
 
+using ::android::hardware::tv::tuner::V1_0::FrontendId;
+using ::android::hardware::tv::tuner::V1_0::IFrontend;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
 
 namespace android {
@@ -29,6 +31,8 @@
 struct JTuner : public RefBase {
     JTuner(JNIEnv *env, jobject thiz);
     sp<ITuner> getTunerService();
+    jobject getFrontendIds();
+    jobject openFrontendById(int id);
 protected:
     virtual ~JTuner();
 
@@ -36,6 +40,7 @@
     jclass mClass;
     jweak mObject;
     static sp<ITuner> mTuner;
+    sp<IFrontend> mFe;
 };
 
 }  // namespace android
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index 8078e36..d4672a9 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -73,7 +73,8 @@
 
                         ~Visualizer();
 
-    virtual status_t    setEnabled(bool enabled);
+    // Declared 'final' because we call this in ~Visualizer().
+    status_t    setEnabled(bool enabled) final;
 
     // maximum capture size in samples
     static uint32_t getMaxCaptureSize() { return VISUALIZER_CAPTURE_SIZE_MAX; }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
index 74bf1a2..de353bf 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -42,6 +42,7 @@
 
 public class MediaInserterTest extends InstrumentationTestCase {
 
+    private static final String TEST_FEATURE_ID = "testFeature";
     private MediaInserter mMediaInserter;
     private static final int TEST_BUFFER_SIZE = 10;
     private @Mock IContentProvider mMockProvider;
@@ -86,7 +87,8 @@
         MockitoAnnotations.initMocks(this);
 
         final ContentProviderClient client = new ContentProviderClient(
-                getInstrumentation().getContext().getContentResolver(), mMockProvider, true);
+                getInstrumentation().getContext().createFeatureContext(TEST_FEATURE_ID)
+                        .getContentResolver(), mMockProvider, true);
         mMediaInserter = new MediaInserter(client, TEST_BUFFER_SIZE);
         mPackageName = getInstrumentation().getContext().getPackageName();
         mFilesCounter = 0;
@@ -142,31 +144,36 @@
         fillBuffer(sVideoUri, TEST_BUFFER_SIZE - 2);
         fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
 
-        verify(mMockProvider, never()).bulkInsert(eq(mPackageName), any(), any());
+        verify(mMockProvider, never()).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any());
     }
 
     @SmallTest
     public void testInsertContentsEqualToBufferSize() throws Exception {
-        when(mMockProvider.bulkInsert(eq(mPackageName), any(), any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any())).thenReturn(1);
 
         fillBuffer(sFilesUri, TEST_BUFFER_SIZE);
         fillBuffer(sAudioUri, TEST_BUFFER_SIZE);
         fillBuffer(sVideoUri, TEST_BUFFER_SIZE);
         fillBuffer(sImagesUri, TEST_BUFFER_SIZE);
 
-        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), any(), any());
+        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any());
     }
 
     @SmallTest
     public void testInsertContentsMoreThanBufferSize() throws Exception {
-        when(mMockProvider.bulkInsert(eq(mPackageName), any(), any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any())).thenReturn(1);
 
         fillBuffer(sFilesUri, TEST_BUFFER_SIZE + 1);
         fillBuffer(sAudioUri, TEST_BUFFER_SIZE + 2);
         fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
         fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
 
-        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), any(), any());
+        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any());
     }
 
     @SmallTest
@@ -176,7 +183,8 @@
 
     @SmallTest
     public void testFlushAllWithSomeContents() throws Exception {
-        when(mMockProvider.bulkInsert(eq(mPackageName), any(), any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any())).thenReturn(1);
 
         fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
         fillBuffer(sAudioUri, TEST_BUFFER_SIZE - 3);
@@ -184,12 +192,14 @@
         fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
         mMediaInserter.flushAll();
 
-        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), any(), any());
+        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any());
     }
 
     @SmallTest
     public void testInsertContentsAfterFlushAll() throws Exception {
-        when(mMockProvider.bulkInsert(eq(mPackageName), any(), any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any())).thenReturn(1);
 
         fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
         fillBuffer(sAudioUri, TEST_BUFFER_SIZE - 3);
@@ -202,15 +212,20 @@
         fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
         fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
 
-        verify(mMockProvider, times(8)).bulkInsert(eq(mPackageName), any(), any());
+        verify(mMockProvider, times(8)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+                any());
     }
 
     @SmallTest
     public void testInsertContentsWithDifferentSizePerContentType() throws Exception {
-        when(mMockProvider.bulkInsert(eq(mPackageName), eqUri(sFilesUri), any())).thenReturn(1);
-        when(mMockProvider.bulkInsert(eq(mPackageName), eqUri(sAudioUri), any())).thenReturn(1);
-        when(mMockProvider.bulkInsert(eq(mPackageName), eqUri(sVideoUri), any())).thenReturn(1);
-        when(mMockProvider.bulkInsert(eq(mPackageName), eqUri(sImagesUri), any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sFilesUri),
+                any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sAudioUri),
+                any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sVideoUri),
+                any())).thenReturn(1);
+        when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sImagesUri),
+                any())).thenReturn(1);
 
         for (int i = 0; i < TEST_BUFFER_SIZE; ++i) {
             fillBuffer(sFilesUri, 1);
@@ -219,9 +234,13 @@
             fillBuffer(sImagesUri, 4);
         }
 
-        verify(mMockProvider, times(1)).bulkInsert(eq(mPackageName), eqUri(sFilesUri), any());
-        verify(mMockProvider, times(2)).bulkInsert(eq(mPackageName), eqUri(sAudioUri), any());
-        verify(mMockProvider, times(3)).bulkInsert(eq(mPackageName), eqUri(sVideoUri), any());
-        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eqUri(sImagesUri), any());
+        verify(mMockProvider, times(1)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+                eqUri(sFilesUri), any());
+        verify(mMockProvider, times(2)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+                eqUri(sAudioUri), any());
+        verify(mMockProvider, times(3)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+                eqUri(sVideoUri), any());
+        verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+                eqUri(sImagesUri), any());
     }
 }
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 9791da6..45f42f1b5 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -16,6 +16,8 @@
 
 #include <jni.h>
 
+#define LOG_TAG "SystemFont"
+
 #include <android/font.h>
 #include <android/font_matcher.h>
 #include <android/system_fonts.h>
@@ -47,9 +49,14 @@
 using XmlCharUniquePtr = std::unique_ptr<xmlChar, XmlCharDeleter>;
 using XmlDocUniquePtr = std::unique_ptr<xmlDoc, XmlDocDeleter>;
 
+struct ParserState {
+    xmlNode* mFontNode = nullptr;
+    XmlCharUniquePtr mLocale;
+};
+
 struct ASystemFontIterator {
     XmlDocUniquePtr mXmlDoc;
-    xmlNode* mFontNode;
+    ParserState state;
 
     // The OEM customization XML.
     XmlDocUniquePtr mCustomizationXmlDoc;
@@ -97,6 +104,7 @@
 
 const xmlChar* FAMILY_TAG = BAD_CAST("family");
 const xmlChar* FONT_TAG = BAD_CAST("font");
+const xmlChar* LOCALE_ATTR_NAME = BAD_CAST("lang");
 
 xmlNode* firstElement(xmlNode* node, const xmlChar* tag) {
     for (xmlNode* child = node->children; child; child = child->next) {
@@ -116,9 +124,9 @@
     return nullptr;
 }
 
-void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, AFont* out,
+void copyFont(const XmlDocUniquePtr& xmlDoc, const ParserState& state, AFont* out,
               const std::string& pathPrefix) {
-    const xmlChar* LOCALE_ATTR_NAME = BAD_CAST("lang");
+    xmlNode* fontNode = state.mFontNode;
     XmlCharUniquePtr filePathStr(
             xmlNodeListGetString(xmlDoc.get(), fontNode->xmlChildrenNode, 1));
     out->mFilePath = pathPrefix + xmlTrim(
@@ -139,9 +147,10 @@
     out->mCollectionIndex =  indexStr ?
             strtol(reinterpret_cast<const char*>(indexStr.get()), nullptr, 10) : 0;
 
-    XmlCharUniquePtr localeStr(xmlGetProp(xmlDoc->parent, LOCALE_ATTR_NAME));
     out->mLocale.reset(
-            localeStr ? new std::string(reinterpret_cast<const char*>(localeStr.get())) : nullptr);
+            state.mLocale ?
+            new std::string(reinterpret_cast<const char*>(state.mLocale.get()))
+            : nullptr);
 
     const xmlChar* TAG_ATTR_NAME = BAD_CAST("tag");
     const xmlChar* STYLEVALUE_ATTR_NAME = BAD_CAST("stylevalue");
@@ -178,25 +187,27 @@
     return S_ISREG(st.st_mode);
 }
 
-xmlNode* findFirstFontNode(const XmlDocUniquePtr& doc) {
+bool findFirstFontNode(const XmlDocUniquePtr& doc, ParserState* state) {
     xmlNode* familySet = xmlDocGetRootElement(doc.get());
     if (familySet == nullptr) {
-        return nullptr;
+        return false;
     }
     xmlNode* family = firstElement(familySet, FAMILY_TAG);
     if (family == nullptr) {
-        return nullptr;
+        return false;
     }
+    state->mLocale.reset(xmlGetProp(family, LOCALE_ATTR_NAME));
 
     xmlNode* font = firstElement(family, FONT_TAG);
     while (font == nullptr) {
         family = nextSibling(family, FAMILY_TAG);
         if (family == nullptr) {
-            return nullptr;
+            return false;
         }
         font = firstElement(family, FONT_TAG);
     }
-    return font;
+    state->mFontNode = font;
+    return font != nullptr;
 }
 
 }  // namespace
@@ -272,38 +283,38 @@
     return result.release();
 }
 
-xmlNode* findNextFontNode(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode) {
-    if (fontNode == nullptr) {
+bool findNextFontNode(const XmlDocUniquePtr& xmlDoc, ParserState* state) {
+    if (state->mFontNode == nullptr) {
         if (!xmlDoc) {
-            return nullptr;  // Already at the end.
+            return false;  // Already at the end.
         } else {
             // First time to query font.
-            return findFirstFontNode(xmlDoc);
+            return findFirstFontNode(xmlDoc, state);
         }
     } else {
-        xmlNode* nextNode = nextSibling(fontNode, FONT_TAG);
+        xmlNode* nextNode = nextSibling(state->mFontNode, FONT_TAG);
         while (nextNode == nullptr) {
-            xmlNode* family = nextSibling(fontNode->parent, FAMILY_TAG);
+            xmlNode* family = nextSibling(state->mFontNode->parent, FAMILY_TAG);
             if (family == nullptr) {
                 break;
             }
+            state->mLocale.reset(xmlGetProp(family, LOCALE_ATTR_NAME));
             nextNode = firstElement(family, FONT_TAG);
         }
-        return nextNode;
+        state->mFontNode = nextNode;
+        return nextNode != nullptr;
     }
 }
 
 AFont* ASystemFontIterator_next(ASystemFontIterator* ite) {
     LOG_ALWAYS_FATAL_IF(ite == nullptr, "nullptr has passed as iterator argument");
     if (ite->mXmlDoc) {
-        ite->mFontNode = findNextFontNode(ite->mXmlDoc, ite->mFontNode);
-        if (ite->mFontNode == nullptr) {
+        if (!findNextFontNode(ite->mXmlDoc, &ite->state)) {
             // Reached end of the XML file. Continue OEM customization.
             ite->mXmlDoc.reset();
-            ite->mFontNode = nullptr;
         } else {
             std::unique_ptr<AFont> font = std::make_unique<AFont>();
-            copyFont(ite->mXmlDoc, ite->mFontNode, font.get(), "/system/fonts/");
+            copyFont(ite->mXmlDoc, ite->state, font.get(), "/system/fonts/");
             if (!isFontFileAvailable(font->mFilePath)) {
                 return ASystemFontIterator_next(ite);
             }
@@ -312,15 +323,13 @@
     }
     if (ite->mCustomizationXmlDoc) {
         // TODO: Filter only customizationType="new-named-family"
-        ite->mFontNode = findNextFontNode(ite->mCustomizationXmlDoc, ite->mFontNode);
-        if (ite->mFontNode == nullptr) {
+        if (!findNextFontNode(ite->mCustomizationXmlDoc, &ite->state)) {
             // Reached end of the XML file. Finishing
             ite->mCustomizationXmlDoc.reset();
-            ite->mFontNode = nullptr;
             return nullptr;
         } else {
             std::unique_ptr<AFont> font = std::make_unique<AFont>();
-            copyFont(ite->mCustomizationXmlDoc, ite->mFontNode, font.get(), "/product/fonts/");
+            copyFont(ite->mCustomizationXmlDoc, ite->state, font.get(), "/product/fonts/");
             if (!isFontFileAvailable(font->mFilePath)) {
                 return ASystemFontIterator_next(ite);
             }
@@ -351,7 +360,7 @@
 
 const char* AFont_getLocale(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
-    return font->mLocale ? nullptr : font->mLocale->c_str();
+    return font->mLocale ? font->mLocale->c_str() : nullptr;
 }
 
 size_t AFont_getCollectionIndex(const AFont* font) {
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 672879a..b2451c9 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -63,6 +63,64 @@
 
 }
 
+android_library {
+    name: "CarSystemUI-tests",
+    manifest: "tests/AndroidManifest.xml",
+    resource_dirs: [
+        "tests/res",
+        "res-keyguard",
+        "res",
+    ],
+    srcs: [
+        "tests/src/**/*.java",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+    static_libs: [
+        "SystemUI-tests",
+        "CarNotificationLib",
+        "SystemUIPluginLib",
+        "SystemUISharedLib",
+        "SettingsLib",
+        "android.car.userlib",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.recyclerview_recyclerview",
+        "androidx.preference_preference",
+        "androidx.appcompat_appcompat",
+        "androidx.mediarouter_mediarouter",
+        "androidx.palette_palette",
+        "androidx.legacy_legacy-preference-v14",
+        "androidx.leanback_leanback",
+        "androidx.slice_slice-core",
+        "androidx.slice_slice-view",
+        "androidx.slice_slice-builders",
+        "androidx.arch.core_core-runtime",
+        "androidx.lifecycle_lifecycle-extensions",
+        "SystemUI-tags",
+        "SystemUI-proto",
+        "metrics-helper-lib",
+        "androidx.test.rules", "hamcrest-library",
+        "mockito-target-inline-minus-junit4",
+        "testables",
+        "truth-prebuilt",
+        "dagger2-2.19",
+        "//external/kotlinc:kotlin-annotations",
+    ],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+        "android.car",
+    ],
+
+    aaptflags: [
+        "--extra-packages",
+        "com.android.systemui",
+    ],
+
+    plugins: ["dagger2-compiler-2.19"],
+}
+
 android_app {
     name: "CarSystemUI",
 
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index fe042fe..329225c 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -40,6 +40,21 @@
          slots that may be reused for things like IME control. -->
     <integer name="config_maxNotificationIcons">0</integer>
 
+    <!--
+        Initial alpha percent value for the background when the notification
+        shade is open. Should be a number between, and inclusive, 0 and 100.
+        If the number is 0, then the background alpha starts off fully
+        transparent. If the number if 100, then the background alpha starts off
+        fully opaque. -->
+    <integer name="config_initialNotificationBackgroundAlpha">0</integer>
+    <!--
+        Final alpha percent value for the background when the notification
+        shade is fully open. Should be a number between, and inclusive, 0 and
+        100. If this value is smaller than
+        config_initialNotificationBackgroundAlpha, the background will default
+        to a constant alpha percent value using the initial alpha. -->
+    <integer name="config_finalNotificationBackgroundAlpha">100</integer>
+
     <!-- SystemUI Services: The classes of the stuff to start. -->
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
copy to packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java
index 4e4c06e..c40eda9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarComponentBinder.java
@@ -14,18 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger;
+package com.android.systemui;
 
-import dagger.Binds;
+import com.android.systemui.dagger.DefaultActivityBinder;
+import com.android.systemui.dagger.DefaultServiceBinder;
+
 import dagger.Module;
 
 /**
- * Dagger Module that collects related sub-modules together.
+ * Supply Activities, Services, and SystemUI Objects for CarSystemUI.
  */
-@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class})
-public abstract class ComponentBinder {
-    /** */
-    @Binds
-    public abstract ContextComponentHelper bindComponentHelper(
-            ContextComponentResolver componentHelper);
+@Module(includes = {
+        DefaultActivityBinder.class,
+        DefaultServiceBinder.class,
+        CarSystemUIBinder.class})
+public class CarComponentBinder {
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 8e0a3eb..4f7b5d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -16,7 +16,16 @@
 
 package com.android.systemui;
 
+import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.navigationbar.car.CarNavigationBar;
+import com.android.systemui.pip.PipUI;
+import com.android.systemui.power.PowerUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsModule;
+import com.android.systemui.statusbar.car.CarStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.volume.VolumeUI;
 
 import dagger.Binds;
 import dagger.Module;
@@ -24,11 +33,71 @@
 import dagger.multibindings.IntoMap;
 
 /** Binder for car specific {@link SystemUI} modules. */
-@Module
+@Module(includes = {RecentsModule.class})
 public abstract class CarSystemUIBinder {
     /** */
     @Binds
     @IntoMap
     @ClassKey(CarNavigationBar.class)
     public abstract SystemUI bindCarNavigationBar(CarNavigationBar sysui);
+
+    /** Inject into GarbageMonitor.Service. */
+    @Binds
+    @IntoMap
+    @ClassKey(GarbageMonitor.Service.class)
+    public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+
+    /** Inject into KeyguardViewMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewMediator.class)
+    public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+
+    /** Inject into LatencyTests. */
+    @Binds
+    @IntoMap
+    @ClassKey(LatencyTester.class)
+    public abstract SystemUI bindLatencyTester(LatencyTester sysui);
+
+    /** Inject into PipUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(PipUI.class)
+    public abstract SystemUI bindPipUI(PipUI sysui);
+
+    /** Inject into PowerUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(PowerUI.class)
+    public abstract SystemUI bindPowerUI(PowerUI sysui);
+
+    /** Inject into Recents. */
+    @Binds
+    @IntoMap
+    @ClassKey(Recents.class)
+    public abstract SystemUI bindRecents(Recents sysui);
+
+    /** Inject into ScreenDecorations. */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenDecorations.class)
+    public abstract SystemUI bindScreenDecorations(ScreenDecorations sysui);
+
+    /** Inject into StatusBar. */
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBar.class)
+    public abstract SystemUI bindsStatusBar(CarStatusBar sysui);
+
+    /** Inject into StatusBarGoogle. */
+    @Binds
+    @IntoMap
+    @ClassKey(CarStatusBar.class)
+    public abstract SystemUI bindsCarStatusBar(CarStatusBar sysui);
+
+    /** Inject into VolumeUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumeUI.class)
+    public abstract SystemUI bindVolumeUI(VolumeUI sysui);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 93e553f..d3a6cde 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -46,8 +46,6 @@
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 @Module
 abstract class CarSystemUIModule {
@@ -105,11 +103,6 @@
     public abstract StatusBar bindStatusBar(CarStatusBar statusBar);
 
     @Binds
-    @IntoMap
-    @ClassKey(StatusBar.class)
-    public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
-
-    @Binds
     abstract VolumeDialogComponent bindVolumeDialogComponent(
             CarVolumeDialogComponent carVolumeDialogComponent);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index c2847c8..51b263e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -29,6 +29,7 @@
 @Singleton
 @Component(
         modules = {
+                CarComponentBinder.class,
                 DependencyProvider.class,
                 DependencyBinder.class,
                 SystemUIFactory.ContextHolder.class,
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 63bc66a..98b91ebd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -25,6 +25,7 @@
 import android.os.ServiceManager;
 import android.view.Display;
 import android.view.Gravity;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
@@ -291,7 +292,8 @@
         }
 
         boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
-        mCarNavigationBarController.setBottomWindowVisibility(!isKeyboardVisible);
+        mCarNavigationBarController.setBottomWindowVisibility(
+                isKeyboardVisible ? View.GONE : View.VISIBLE);
     }
 
     private void updateNavBarForKeyguardContent() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index f59f886..6bed69b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -98,31 +98,30 @@
     }
 
     /** Toggles the bottom nav bar visibility. */
-    public boolean setBottomWindowVisibility(boolean isVisible) {
-        return setWindowVisibility(getBottomWindow(), isVisible);
+    public boolean setBottomWindowVisibility(@View.Visibility int visibility) {
+        return setWindowVisibility(getBottomWindow(), visibility);
     }
 
     /** Toggles the left nav bar visibility. */
-    public boolean setLeftWindowVisibility(boolean isVisible) {
-        return setWindowVisibility(getLeftWindow(), isVisible);
+    public boolean setLeftWindowVisibility(@View.Visibility int visibility) {
+        return setWindowVisibility(getLeftWindow(), visibility);
     }
 
     /** Toggles the right nav bar visibility. */
-    public boolean setRightWindowVisibility(boolean isVisible) {
-        return setWindowVisibility(getRightWindow(), isVisible);
+    public boolean setRightWindowVisibility(@View.Visibility int visibility) {
+        return setWindowVisibility(getRightWindow(), visibility);
     }
 
-    private boolean setWindowVisibility(ViewGroup window, boolean isVisible) {
+    private boolean setWindowVisibility(ViewGroup window, @View.Visibility int visibility) {
         if (window == null) {
             return false;
         }
 
-        int newVisibility = isVisible ? View.VISIBLE : View.GONE;
-        if (window.getVisibility() == newVisibility) {
+        if (window.getVisibility() == visibility) {
             return false;
         }
 
-        window.setVisibility(newVisibility);
+        window.setVisibility(visibility);
         return true;
     }
 
@@ -228,6 +227,63 @@
         }
     }
 
+    /**
+     * Shows all of the keyguard specific buttons on the valid instances of
+     * {@link CarNavigationBarView}.
+     */
+    public void showAllKeyguardButtons(boolean isSetUp) {
+        checkAllBars(isSetUp);
+        if (mTopView != null) {
+            mTopView.showKeyguardButtons();
+        }
+        if (mBottomView != null) {
+            mBottomView.showKeyguardButtons();
+        }
+        if (mLeftView != null) {
+            mLeftView.showKeyguardButtons();
+        }
+        if (mRightView != null) {
+            mRightView.showKeyguardButtons();
+        }
+    }
+
+    /**
+     * Hides all of the keyguard specific buttons on the valid instances of
+     * {@link CarNavigationBarView}.
+     */
+    public void hideAllKeyguardButtons(boolean isSetUp) {
+        checkAllBars(isSetUp);
+        if (mTopView != null) {
+            mTopView.hideKeyguardButtons();
+        }
+        if (mBottomView != null) {
+            mBottomView.hideKeyguardButtons();
+        }
+        if (mLeftView != null) {
+            mLeftView.hideKeyguardButtons();
+        }
+        if (mRightView != null) {
+            mRightView.hideKeyguardButtons();
+        }
+    }
+
+    /** Toggles whether the notifications icon has an unseen indicator or not. */
+    public void toggleAllNotificationsUnseenIndicator(boolean isSetUp, boolean hasUnseen) {
+        checkAllBars(isSetUp);
+        if (mTopView != null) {
+            mTopView.toggleNotificationUnseenIndicator(hasUnseen);
+        }
+        if (mBottomView != null) {
+            mBottomView.toggleNotificationUnseenIndicator(hasUnseen);
+        }
+        if (mLeftView != null) {
+            mLeftView.toggleNotificationUnseenIndicator(hasUnseen);
+        }
+        if (mRightView != null) {
+            mRightView.toggleNotificationUnseenIndicator(hasUnseen);
+        }
+    }
+
     /** Interface for controlling the notifications shade. */
     public interface NotificationsShadeController {
         /** Toggles the visibility of the notifications shade. */
@@ -244,4 +300,11 @@
             }
         }
     }
+
+    private void checkAllBars(boolean isSetUp) {
+        mTopView = getTopBar(isSetUp);
+        mBottomView = getBottomBar(isSetUp);
+        mLeftView = getLeftBar(isSetUp);
+        mRightView = getRightBar(isSetUp);
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
index 24f8b74..c245508 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarView.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.car.CarNavigationBarController.NotificationsShadeController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 
 /**
@@ -35,7 +36,7 @@
 public class CarNavigationBarView extends LinearLayout {
     private View mNavButtons;
     private CarNavigationButton mNotificationsButton;
-    private CarNavigationBarController.NotificationsShadeController mNotificationsShadeController;
+    private NotificationsShadeController mNotificationsShadeController;
     private Context mContext;
     private View mLockScreenButtons;
     // used to wire in open/close gestures for notifications
@@ -81,13 +82,18 @@
         return super.onInterceptTouchEvent(ev);
     }
 
-    public void setNotificationsPanelController(
-            CarNavigationBarController.NotificationsShadeController controller) {
+    /** Sets the notifications panel controller. */
+    public void setNotificationsPanelController(NotificationsShadeController controller) {
         mNotificationsShadeController = controller;
     }
 
+    /** Gets the notifications panel controller. */
+    public NotificationsShadeController getNotificationsPanelController() {
+        return mNotificationsShadeController;
+    }
+
     /**
-     * Set a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
+     * Sets a touch listener that will be called from onInterceptTouchEvent and onTouchEvent
      *
      * @param statusBarWindowTouchListener The listener to call from touch and intercept touch
      */
@@ -95,6 +101,11 @@
         mStatusBarWindowTouchListener = statusBarWindowTouchListener;
     }
 
+    /** Gets the touch listener that will be called from onInterceptTouchEvent and onTouchEvent. */
+    public OnTouchListener getStatusBarWindowTouchListener() {
+        return mStatusBarWindowTouchListener;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (mStatusBarWindowTouchListener != null) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
index 40823ab..922bfff 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
@@ -150,6 +150,11 @@
         updateImage();
     }
 
+    /** Gets whether the icon is in an unseen state. */
+    public boolean getUnseen() {
+        return mHasUnseen;
+    }
+
     private void updateImage() {
         if (mHasUnseen) {
             setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index a1a6ab4..d548fa1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -27,11 +27,11 @@
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.CarUxRestrictionsManager;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
-import android.car.trust.CarTrustAgentEnrollmentManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.PowerManager;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -107,6 +107,8 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -155,6 +157,9 @@
     private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
     private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
 
+    private float mBackgroundAlphaDiff;
+    private float mInitialBackgroundAlpha;
+
     private FullscreenUserSwitcher mFullscreenUserSwitcher;
 
     private CarBatteryController mCarBatteryController;
@@ -162,13 +167,7 @@
     private Drawable mNotificationPanelBackground;
 
     private ViewGroup mTopNavigationBarContainer;
-    private ViewGroup mNavigationBarWindow;
-    private ViewGroup mLeftNavigationBarWindow;
-    private ViewGroup mRightNavigationBarWindow;
     private CarNavigationBarView mTopNavigationBarView;
-    private CarNavigationBarView mNavigationBarView;
-    private CarNavigationBarView mLeftNavigationBarView;
-    private CarNavigationBarView mRightNavigationBarView;
 
     private final Object mQueueLock = new Object();
     private final CarNavigationBarController mCarNavigationBarController;
@@ -221,6 +220,8 @@
     // Whether heads-up notifications should be shown when shade is open.
     private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
 
+    private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
+
     private final CarPowerStateListener mCarPowerStateListener =
             (int state) -> {
                 // When the car powers on, clear all notifications and mute/unread states.
@@ -297,6 +298,9 @@
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            DozeServiceHost dozeServiceHost,
+            PowerManager powerManager,
+            DozeScrimController dozeScrimController,
 
             /* Car Settings injected components. */
             CarNavigationBarController carNavigationBarController) {
@@ -360,7 +364,10 @@
                 dozeParameters,
                 scrimController,
                 lockscreenWallpaperLazy,
-                biometricUnlockControllerLazy);
+                biometricUnlockControllerLazy,
+                dozeServiceHost,
+                powerManager,
+                dozeScrimController);
         mScrimController = scrimController;
         mCarNavigationBarController = carNavigationBarController;
     }
@@ -377,6 +384,25 @@
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
         mScreenLifecycle.addObserver(mScreenObserver);
 
+      	// Notification bar related setup.
+        mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
+            R.integer.config_initialNotificationBackgroundAlpha) / 100;
+        if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
+            throw new RuntimeException(
+              "Unable to setup notification bar due to incorrect initial background alpha"
+                      + " percentage");
+        }
+        float finalBackgroundAlpha = Math.max(
+            mInitialBackgroundAlpha,
+            (float) mContext.getResources().getInteger(
+                R.integer.config_finalNotificationBackgroundAlpha) / 100);
+        if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
+            throw new RuntimeException(
+              "Unable to setup notification bar due to incorrect final background alpha"
+                      + " percentage");
+        }
+        mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
+
         super.start();
 
         mNotificationPanel.setScrollingEnabled(true);
@@ -394,30 +420,32 @@
                 new DeviceProvisionedController.DeviceProvisionedListener() {
                     @Override
                     public void onUserSetupChanged() {
-                        mHandler.post(() -> restartNavBarsIfNecessary());
+                        mHandler.post(() -> resetSystemBarsIfNecessary());
                     }
 
                     @Override
                     public void onUserSwitched() {
-                        mHandler.post(() -> restartNavBarsIfNecessary());
+                        mHandler.post(() -> resetSystemBarsIfNecessary());
                     }
                 });
 
+        // Used by onDrivingStateChanged and it can be called inside
+        // DrivingStateHelper.connectToCarService()
+        mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
+
         // Register a listener for driving state changes.
         mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
         mDrivingStateHelper.connectToCarService();
 
         mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
         mPowerManagerHelper.connectToCarService();
-
-        mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
     }
 
-    private void restartNavBarsIfNecessary() {
+    private void resetSystemBarsIfNecessary() {
         boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
         if (mDeviceIsSetUpForUser != currentUserSetup) {
             mDeviceIsSetUpForUser = currentUserSetup;
-            restartNavBars();
+            resetSystemBars();
         }
     }
 
@@ -425,19 +453,9 @@
      * Remove all content from navbars and rebuild them. Used to allow for different nav bars
      * before and after the device is provisioned. . Also for change of density and font size.
      */
-    private void restartNavBars() {
+    private void resetSystemBars() {
         mCarFacetButtonController.removeAll();
 
-        if (mNavigationBarWindow != null) {
-            mNavigationBarView = null;
-        }
-        if (mLeftNavigationBarWindow != null) {
-            mLeftNavigationBarView = null;
-        }
-        if (mRightNavigationBarWindow != null) {
-            mRightNavigationBarView = null;
-        }
-
         buildNavBarContent();
         // CarFacetButtonController was reset therefore we need to re-add the status bar elements
         // to the controller.
@@ -449,51 +467,22 @@
      * the full screen user selector is shown.
      */
     void setNavBarVisibility(@View.Visibility int visibility) {
-        if (mNavigationBarWindow != null) {
-            mNavigationBarWindow.setVisibility(visibility);
-        }
-        if (mLeftNavigationBarWindow != null) {
-            mLeftNavigationBarWindow.setVisibility(visibility);
-        }
-        if (mRightNavigationBarWindow != null) {
-            mRightNavigationBarWindow.setVisibility(visibility);
-        }
+        mCarNavigationBarController.setBottomWindowVisibility(visibility);
+        mCarNavigationBarController.setLeftWindowVisibility(visibility);
+        mCarNavigationBarController.setRightWindowVisibility(visibility);
     }
 
     @Override
     public boolean hideKeyguard() {
         boolean result = super.hideKeyguard();
-        if (mNavigationBarView != null) {
-            mNavigationBarView.hideKeyguardButtons();
-        }
-        if (mLeftNavigationBarView != null) {
-            mLeftNavigationBarView.hideKeyguardButtons();
-        }
-        if (mRightNavigationBarView != null) {
-            mRightNavigationBarView.hideKeyguardButtons();
-        }
+        mCarNavigationBarController.hideAllKeyguardButtons(mDeviceIsSetUpForUser);
         return result;
     }
 
     @Override
     public void showKeyguard() {
         super.showKeyguard();
-        updateNavBarForKeyguardContent();
-    }
-
-    /**
-     * Switch to the keyguard applicable content contained in the nav bars
-     */
-    private void updateNavBarForKeyguardContent() {
-        if (mNavigationBarView != null) {
-            mNavigationBarView.showKeyguardButtons();
-        }
-        if (mLeftNavigationBarView != null) {
-            mLeftNavigationBarView.showKeyguardButtons();
-        }
-        if (mRightNavigationBarView != null) {
-            mRightNavigationBarView.showKeyguardButtons();
-        }
+        mCarNavigationBarController.showAllKeyguardButtons(mDeviceIsSetUpForUser);
     }
 
     @Override
@@ -586,31 +575,31 @@
                 animateCollapsePanels();
             }
         });
-        Car car = Car.createCar(mContext);
-        CarUxRestrictionsManager carUxRestrictionsManager = (CarUxRestrictionsManager)
-                car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
         CarNotificationListener carNotificationListener = new CarNotificationListener();
-        CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper =
-                new CarUxRestrictionManagerWrapper();
-        carUxRestrictionManagerWrapper.setCarUxRestrictionsManager(carUxRestrictionsManager);
+        mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
+        // This can take time if car service is not ready up to this time.
+        // TODO(b/142808072) Refactor CarUxRestrictionManagerWrapper to allow setting
+        // CarUxRestrictionsManager later and switch to Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT.
+        Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                (car, ready) -> {
+                    if (!ready) {
+                        return;
+                    }
+                    CarUxRestrictionsManager carUxRestrictionsManager =
+                            (CarUxRestrictionsManager)
+                                    car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+                    mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
+                            carUxRestrictionsManager);
+                });
 
         mNotificationDataManager = new NotificationDataManager();
         mNotificationDataManager.setOnUnseenCountUpdateListener(
                 () -> {
-                    if (mNavigationBarView != null && mNotificationDataManager != null) {
-                        Boolean hasUnseen =
+                    if (mNotificationDataManager != null) {
+                        boolean hasUnseen =
                                 mNotificationDataManager.getUnseenNotificationCount() > 0;
-                        if (mNavigationBarView != null) {
-                            mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
-                        }
-
-                        if (mLeftNavigationBarView != null) {
-                            mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
-                        }
-
-                        if (mRightNavigationBarView != null) {
-                            mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
-                        }
+                        mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(
+                                mDeviceIsSetUpForUser, hasUnseen);
                     }
                 });
 
@@ -621,7 +610,7 @@
                         mNotificationClickHandlerFactory, mNotificationDataManager);
         mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
 
-        carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper,
+        carNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
                 carHeadsUpNotificationManager, mNotificationDataManager);
 
         mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
@@ -721,7 +710,7 @@
                 mNotificationView,
                 PreprocessingManager.getInstance(mContext),
                 carNotificationListener,
-                carUxRestrictionManagerWrapper,
+                mCarUxRestrictionManagerWrapper,
                 mNotificationDataManager);
         mNotificationViewController.enable();
     }
@@ -864,37 +853,27 @@
 
     @Override
     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
-        buildNavBarWindows();
+        mTopNavigationBarContainer = mStatusBarWindow
+                .findViewById(R.id.car_top_navigation_bar_container);
+
         buildNavBarContent();
     }
 
     private void buildNavBarContent() {
         buildTopBar();
 
-        mNavigationBarView = mCarNavigationBarController.getBottomBar(mDeviceIsSetUpForUser);
         mCarNavigationBarController.registerBottomBarTouchListener(
                 mNavBarNotificationTouchListener);
 
-        mLeftNavigationBarView = mCarNavigationBarController.getLeftBar(mDeviceIsSetUpForUser);
         mCarNavigationBarController.registerLeftBarTouchListener(
                 mNavBarNotificationTouchListener);
 
-        mRightNavigationBarView = mCarNavigationBarController.getLeftBar(mDeviceIsSetUpForUser);
         mCarNavigationBarController.registerRightBarTouchListener(
                 mNavBarNotificationTouchListener);
 
         mCarNavigationBarController.registerNotificationController(() -> togglePanel());
     }
 
-    private void buildNavBarWindows() {
-        mTopNavigationBarContainer = mStatusBarWindow
-                .findViewById(R.id.car_top_navigation_bar_container);
-
-        mNavigationBarWindow = mCarNavigationBarController.getBottomWindow();
-        mLeftNavigationBarWindow = mCarNavigationBarController.getLeftWindow();
-        mRightNavigationBarWindow = mCarNavigationBarController.getRightWindow();
-    }
-
     private void buildTopBar() {
         mTopNavigationBarContainer.removeAllViews();
         mTopNavigationBarView = mCarNavigationBarController.getTopBar(mDeviceIsSetUpForUser);
@@ -980,12 +959,8 @@
         UserSwitcherController userSwitcherController =
                 Dependency.get(UserSwitcherController.class);
         if (userSwitcherController.useFullscreenUserSwitcher()) {
-            Car car = Car.createCar(mContext);
-            CarTrustAgentEnrollmentManager enrollmentManager = (CarTrustAgentEnrollmentManager) car
-                    .getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
             mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
-                    mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub),
-                    enrollmentManager, mContext);
+                    mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
         } else {
             super.createUserSwitcher();
         }
@@ -1068,7 +1043,7 @@
     @Override
     public void onDensityOrFontScaleChanged() {
         super.onDensityOrFontScaleChanged();
-        restartNavBars();
+        resetSystemBars();
         // Need to update the background on density changed in case the change was due to night
         // mode.
         mNotificationPanelBackground = getDefaultWallpaper();
@@ -1096,17 +1071,22 @@
             mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
         }
         if (mNotificationView.getHeight() > 0) {
-            // Calculates the alpha value for the background based on how much of the notification
-            // shade is visible to the user. When the notification shade is completely open then
-            // alpha value will be 1.
-            float alpha = (float) height / mNotificationView.getHeight();
             Drawable background = mNotificationView.getBackground().mutate();
-
-            background.setAlpha((int) (alpha * 255));
+            background.setAlpha((int) (getBackgroundAlpha(height) * 255));
             mNotificationView.setBackground(background);
         }
     }
 
+    /**
+     * Calculates the alpha value for the background based on how much of the notification
+     * shade is visible to the user. When the notification shade is completely open then
+     * alpha value will be 1.
+     */
+    private float getBackgroundAlpha(int height) {
+        return mInitialBackgroundAlpha +
+            ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
+    }
+
     @Override
     public void onConfigChanged(Configuration newConfig) {
         super.onConfigChanged(newConfig);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
index a442426..cd87e78 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
@@ -17,14 +17,11 @@
 package com.android.systemui.statusbar.car;
 
 import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.CarDrivingStateManager;
 import android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -55,16 +52,11 @@
         if (mDrivingStateManager == null) {
             return false;
         }
-        try {
-            CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState();
-            if (currentState != null) {
-                return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING
-                        || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING;
-            }
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Cannot determine current driving state. Car not connected", e);
+        CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState();
+        if (currentState != null) {
+            return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING
+                    || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING;
         }
-
         return false; // Default to false.
     }
 
@@ -72,55 +64,25 @@
      * Establishes connection with the Car service.
      */
     public void connectToCarService() {
-        mCar = Car.createCar(mContext, mCarConnectionListener);
-        if (mCar != null) {
-            mCar.connect();
-        }
+        mCar = Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                mCarServiceLifecycleListener);
     }
 
-    /**
-     * Disconnects from Car service and cleans up listeners.
-     */
-    public void disconnectFromCarService() {
-        if (mCar != null) {
-            mCar.disconnect();
+    private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+        if (!ready) {
+            return;
         }
-    }
-
-    private final ServiceConnection mCarConnectionListener =
-            new ServiceConnection() {
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    logD("Car Service connected");
-                    try {
-                        mDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
-                                Car.CAR_DRIVING_STATE_SERVICE);
-                        if (mDrivingStateManager != null) {
-                            mDrivingStateManager.registerListener(mDrivingStateHandler);
-                            mDrivingStateHandler.onDrivingStateChanged(
-                                    mDrivingStateManager.getCurrentCarDrivingState());
-                        } else {
-                            Log.e(TAG, "CarDrivingStateService service not available");
-                        }
-                    } catch (CarNotConnectedException e) {
-                        Log.e(TAG, "Car not connected", e);
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    destroyDrivingStateManager();
-                }
-            };
-
-    private void destroyDrivingStateManager() {
-        try {
-            if (mDrivingStateManager != null) {
-                mDrivingStateManager.unregisterListener();
-            }
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Error unregistering listeners", e);
+        logD("Car Service connected");
+        mDrivingStateManager = (CarDrivingStateManager) car.getCarManager(
+                Car.CAR_DRIVING_STATE_SERVICE);
+        if (mDrivingStateManager != null) {
+            mDrivingStateManager.registerListener(mDrivingStateHandler);
+            mDrivingStateHandler.onDrivingStateChanged(
+                    mDrivingStateManager.getCurrentCarDrivingState());
+        } else {
+            Log.e(TAG, "CarDrivingStateService service not available");
         }
-    }
+    };
 
     private void logD(String message) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 0f7c1ee..31aced0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.car.Car;
 import android.car.trust.CarTrustAgentEnrollmentManager;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
@@ -50,7 +51,7 @@
     private final CarStatusBar mStatusBar;
     private final Context mContext;
     private final UserManager mUserManager;
-    private final CarTrustAgentEnrollmentManager mEnrollmentManager;
+    private CarTrustAgentEnrollmentManager mEnrollmentManager;
     private CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
     private UserGridRecyclerView.UserRecord mSelectedUser;
     private CarUserManagerHelper mCarUserManagerHelper;
@@ -64,13 +65,11 @@
             mContext.unregisterReceiver(mUserUnlockReceiver);
         }
     };
+    private final Car mCar;
 
-
-    public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub,
-            CarTrustAgentEnrollmentManager enrollmentManager, Context context) {
+    public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, Context context) {
         mStatusBar = statusBar;
         mParent = containerStub.inflate();
-        mEnrollmentManager = enrollmentManager;
         mContext = context;
 
         View container = mParent.findViewById(R.id.container);
@@ -86,6 +85,15 @@
         mUnlockDialogHelper = new CarTrustAgentUnlockDialogHelper(mContext);
         mUserManager = mContext.getSystemService(UserManager.class);
 
+        mCar = Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                (car, ready) -> {
+                    if (!ready) {
+                        return;
+                    }
+                    mEnrollmentManager = (CarTrustAgentEnrollmentManager) car
+                            .getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
+                });
+
         mShortAnimDuration = container.getResources()
                 .getInteger(android.R.integer.config_shortAnimTime);
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
@@ -201,6 +209,9 @@
     }
 
     private boolean hasTrustedDevice(int uid) {
+        if (mEnrollmentManager == null) { // car service not ready, so it cannot be available.
+            return false;
+        }
         return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
index 8de1439..a27dd34 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
@@ -18,13 +18,10 @@
 
 import android.annotation.NonNull;
 import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
 import android.util.Log;
 
 /**
@@ -39,55 +36,30 @@
     private Car mCar;
     private CarPowerManager mCarPowerManager;
 
-    private final ServiceConnection mCarConnectionListener =
-            new ServiceConnection() {
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    Log.d(TAG, "Car Service connected");
-                    try {
-                        mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
-                        if (mCarPowerManager != null) {
-                            mCarPowerManager.setListener(mCarPowerStateListener);
-                        } else {
-                            Log.e(TAG, "CarPowerManager service not available");
-                        }
-                    } catch (CarNotConnectedException e) {
-                        Log.e(TAG, "Car not connected", e);
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    destroyCarPowerManager();
-                }
-            };
+    private final CarServiceLifecycleListener mCarServiceLifecycleListener;
 
     PowerManagerHelper(Context context, @NonNull CarPowerStateListener listener) {
         mContext = context;
         mCarPowerStateListener = listener;
+        mCarServiceLifecycleListener = (car, ready) -> {
+            if (!ready) {
+                return;
+            }
+            Log.d(TAG, "Car Service connected");
+            mCarPowerManager = (CarPowerManager) car.getCarManager(Car.POWER_SERVICE);
+            if (mCarPowerManager != null) {
+                mCarPowerManager.setListener(mCarPowerStateListener);
+            } else {
+                Log.e(TAG, "CarPowerManager service not available");
+            }
+        };
     }
 
     /**
      * Connect to Car service.
      */
     void connectToCarService() {
-        mCar = Car.createCar(mContext, mCarConnectionListener);
-        if (mCar != null) {
-            mCar.connect();
-        }
-    }
-
-    /**
-     * Disconnects from Car service.
-     */
-    void disconnectFromCarService() {
-        if (mCar != null) {
-            mCar.disconnect();
-        }
-    }
-
-    private void destroyCarPowerManager() {
-        if (mCarPowerManager != null) {
-            mCarPowerManager.clearListener();
-        }
+        mCar = Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                mCarServiceLifecycleListener);
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 3b48259..05657ff 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -32,7 +32,6 @@
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.UserHandle;
@@ -67,6 +66,7 @@
     private CarUserManagerHelper mCarUserManagerHelper;
     private UserManager mUserManager;
     private Context mContext;
+    private UserIconProvider mUserIconProvider;
 
     private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
         @Override
@@ -80,6 +80,7 @@
         mContext = context;
         mCarUserManagerHelper = new CarUserManagerHelper(mContext);
         mUserManager = UserManager.get(mContext);
+        mUserIconProvider = new UserIconProvider();
 
         addItemDecoration(new ItemSpacingDecoration(mContext.getResources().getDimensionPixelSize(
                 R.dimen.car_user_switcher_vertical_spacing_between_users)));
@@ -252,9 +253,7 @@
         @Override
         public void onBindViewHolder(UserAdapterViewHolder holder, int position) {
             UserRecord userRecord = mUsers.get(position);
-            RoundedBitmapDrawable circleIcon = RoundedBitmapDrawableFactory.create(mRes,
-                    getUserRecordIcon(userRecord));
-            circleIcon.setCircular(true);
+            RoundedBitmapDrawable circleIcon = getCircularUserRecordIcon(userRecord);
             holder.mUserAvatarImageView.setImageDrawable(circleIcon);
             holder.mUserNameTextView.setText(userRecord.mInfo.name);
 
@@ -336,17 +335,20 @@
             }
         }
 
-        private Bitmap getUserRecordIcon(UserRecord userRecord) {
+        private RoundedBitmapDrawable getCircularUserRecordIcon(UserRecord userRecord) {
+            Resources resources = mContext.getResources();
+            RoundedBitmapDrawable circleIcon;
             if (userRecord.mIsStartGuestSession) {
-                return mCarUserManagerHelper.getGuestDefaultIcon();
+                circleIcon = mUserIconProvider.getRoundedGuestDefaultIcon(resources);
+            } else if (userRecord.mIsAddUser) {
+                circleIcon = RoundedBitmapDrawableFactory.create(mRes, UserIcons.convertToBitmap(
+                        mContext.getDrawable(R.drawable.car_add_circle_round)));
+                circleIcon.setCircular(true);
+            } else {
+                circleIcon = mUserIconProvider.getRoundedUserIcon(userRecord.mInfo, mContext);
             }
 
-            if (userRecord.mIsAddUser) {
-                return UserIcons.convertToBitmap(mContext
-                        .getDrawable(R.drawable.car_add_circle_round));
-            }
-
-            return mCarUserManagerHelper.getUserIcon(userRecord.mInfo);
+            return circleIcon;
         }
 
         @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java
new file mode 100644
index 0000000..9464eab
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.internal.util.UserIcons;
+import com.android.systemui.R;
+
+/**
+ * Simple class for providing icons for users.
+ */
+public class UserIconProvider {
+    /**
+     * Gets a scaled rounded icon for the given user.  If a user does not have an icon saved, this
+     * method will default to a generic icon and update UserManager to use that icon.
+     *
+     * @param userInfo User for which the icon is requested.
+     * @param context Context to use for resources
+     * @return {@link RoundedBitmapDrawable} representing the icon for the user.
+     */
+    public RoundedBitmapDrawable getRoundedUserIcon(UserInfo userInfo, Context context) {
+        UserManager userManager = UserManager.get(context);
+        Resources res = context.getResources();
+        Bitmap icon = userManager.getUserIcon(userInfo.id);
+
+        if (icon == null) {
+            icon = assignDefaultIcon(userManager, res, userInfo);
+        }
+
+        return createScaledRoundIcon(res, icon);
+    }
+
+    /** Returns a scaled, rounded, default icon for the Guest user */
+    public RoundedBitmapDrawable getRoundedGuestDefaultIcon(Resources resources) {
+        return createScaledRoundIcon(resources, getGuestUserDefaultIcon(resources));
+    }
+
+    private RoundedBitmapDrawable createScaledRoundIcon(Resources resources, Bitmap icon) {
+        BitmapDrawable scaledIcon = scaleUserIcon(resources, icon);
+        RoundedBitmapDrawable circleIcon =
+                RoundedBitmapDrawableFactory.create(resources, scaledIcon.getBitmap());
+        circleIcon.setCircular(true);
+        return circleIcon;
+    }
+
+    /**
+     * Returns a {@link Drawable} for the given {@code icon} scaled to the appropriate size.
+     */
+    private static BitmapDrawable scaleUserIcon(Resources res, Bitmap icon) {
+        int desiredSize = res.getDimensionPixelSize(R.dimen.car_primary_icon_size);
+        Bitmap scaledIcon =
+                Bitmap.createScaledBitmap(icon, desiredSize, desiredSize, /*filter=*/ true);
+        return new BitmapDrawable(res, scaledIcon);
+    }
+
+    /**
+     * Assigns a default icon to a user according to the user's id. Handles Guest icon and non-guest
+     * user icons.
+     *
+     * @param userManager {@link UserManager} to set user icon
+     * @param resources {@link Resources} to grab icons from
+     * @param userInfo User whose avatar is set to default icon.
+     * @return Bitmap of the user icon.
+     */
+    private Bitmap assignDefaultIcon(
+            UserManager userManager, Resources resources, UserInfo userInfo) {
+        Bitmap bitmap = userInfo.isGuest()
+                ? getGuestUserDefaultIcon(resources)
+                : getUserDefaultIcon(resources, userInfo.id);
+        userManager.setUserIcon(userInfo.id, bitmap);
+        return bitmap;
+    }
+
+    /**
+     * Gets a bitmap representing the user's default avatar.
+     *
+     * @param resources The resources to pull from
+     * @param id The id of the user to get the icon for.  Pass {@link UserHandle#USER_NULL} for
+     *           Guest user.
+     * @return Default user icon
+     */
+    private Bitmap getUserDefaultIcon(Resources resources, @UserIdInt int id) {
+        return UserIcons.convertToBitmap(
+                UserIcons.getDefaultUserIcon(resources, id, /* light= */ false));
+    }
+
+    private Bitmap getGuestUserDefaultIcon(Resources resources) {
+        return getUserDefaultIcon(resources, UserHandle.USER_NULL);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
index e81be1b..41914d2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
@@ -20,15 +20,13 @@
 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
 
 import android.car.Car;
+import android.car.Car.CarServiceLifecycleListener;
 import android.car.VehicleUnit;
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.hvac.CarHvacManager;
 import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ServiceConnection;
 import android.os.Handler;
-import android.os.IBinder;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -54,6 +52,7 @@
     private Car mCar;
     private CarHvacManager mHvacManager;
     private HashMap<HvacKey, List<TemperatureView>> mTempComponents = new HashMap<>();
+
     /**
      * Callback for getting changes from {@link CarHvacManager} and setting the UI elements to
      * match.
@@ -85,39 +84,17 @@
                     + " zone: " + zone);
         }
     };
-    /**
-     * If the connection to car service goes away then restart it.
-     */
-    private final IBinder.DeathRecipient mRestart = new IBinder.DeathRecipient() {
-        @Override
-        public void binderDied() {
-            Log.d(TAG, "Death of HVAC triggering a restart");
-            if (mCar != null) {
-                mCar.disconnect();
-            }
-            destroyHvacManager();
-            mHandler.postDelayed(() -> mCar.connect(), BIND_TO_HVAC_RETRY_DELAY);
-        }
-    };
-    /**
-     * Registers callbacks and initializes components upon connection.
-     */
-    private ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                service.linkToDeath(mRestart, 0);
-                mHvacManager = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE);
-                mHvacManager.registerCallback(mHardwareCallback);
-                initComponents();
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to correctly connect to HVAC", e);
-            }
-        }
 
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            destroyHvacManager();
+    private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+        if (!ready) {
+            return;
+        }
+        try {
+            mHvacManager = (CarHvacManager) car.getCarManager(Car.HVAC_SERVICE);
+            mHvacManager.registerCallback(mHardwareCallback);
+            initComponents();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to correctly connect to HVAC", e);
         }
     };
 
@@ -132,18 +109,8 @@
      */
     public void connectToCarService() {
         mHandler = new Handler();
-        mCar = Car.createCar(mContext, mServiceConnection, mHandler);
-        if (mCar != null) {
-            // note: this connect call handles the retries
-            mCar.connect();
-        }
-    }
-
-    private void destroyHvacManager() {
-        if (mHvacManager != null) {
-            mHvacManager.unregisterCallback(mHardwareCallback);
-            mHvacManager = null;
-        }
+        mCar = Car.createCar(mContext, /* handler= */ mHandler, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                mCarServiceLifecycleListener);
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 22c7c7a..d979bad 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -24,12 +24,10 @@
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
 import android.car.media.CarAudioManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.ServiceConnection;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Color;
@@ -39,7 +37,6 @@
 import android.media.AudioManager;
 import android.os.Debug;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.util.AttributeSet;
@@ -146,42 +143,30 @@
     private boolean mDismissing;
     private boolean mExpanded;
     private View mExpandIcon;
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mExpanded = false;
-                mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
-                int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
-                // Populates volume slider items from volume groups to UI.
-                for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
-                    VolumeItem volumeItem = getVolumeItemForUsages(
-                            mCarAudioManager.getUsagesForVolumeGroupId(groupId));
-                    mAvailableVolumeItems.add(volumeItem);
-                    // The first one is the default item.
-                    if (groupId == 0) {
-                        setuptListItem(0);
-                    }
-                }
 
-                // If list is already initiated, update its content.
-                if (mVolumeItemsAdapter != null) {
-                    mVolumeItemsAdapter.notifyDataSetChanged();
-                }
-                mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "Car is not connected!", e);
+    private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+        if (!ready) {
+            return;
+        }
+        mExpanded = false;
+        mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+        int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+        // Populates volume slider items from volume groups to UI.
+        for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+            VolumeItem volumeItem = getVolumeItemForUsages(
+                    mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+            mAvailableVolumeItems.add(volumeItem);
+            // The first one is the default item.
+            if (groupId == 0) {
+                setuptListItem(0);
             }
         }
 
-        /**
-         * This does not get called when service is properly disconnected.
-         * So we need to also handle cleanups in destroy().
-         */
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            cleanupAudioManager();
+        // If list is already initiated, update its content.
+        if (mVolumeItemsAdapter != null) {
+            mVolumeItemsAdapter.notifyDataSetChanged();
         }
+        mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
     };
 
     private void setuptListItem(int groupId) {
@@ -196,25 +181,14 @@
     public CarVolumeDialogImpl(Context context) {
         mContext = context;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mCar = Car.createCar(mContext, mServiceConnection);
     }
 
     private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
-        try {
-            return carAudioManager.getGroupVolume(volumeGroupId);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!", e);
-        }
-        return 0;
+        return carAudioManager.getGroupVolume(volumeGroupId);
     }
 
     private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
-        try {
-            return carAudioManager.getGroupMaxVolume(volumeGroupId);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!", e);
-        }
-        return 0;
+        return carAudioManager.getGroupMaxVolume(volumeGroupId);
     }
 
     /**
@@ -224,8 +198,8 @@
     @Override
     public void init(int windowType, Callback callback) {
         initDialog();
-
-        mCar.connect();
+        mCar = Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                mCarServiceLifecycleListener);
     }
 
     @Override
@@ -235,7 +209,10 @@
         cleanupAudioManager();
         // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
         // audio manager beforehand.
-        mCar.disconnect();
+        if (mCar != null) {
+            mCar.disconnect();
+            mCar = null;
+        }
     }
 
     private void initDialog() {
@@ -605,18 +582,14 @@
                 // sent back down again.
                 return;
             }
-            try {
-                if (mCarAudioManager == null) {
-                    Log.w(TAG, "Ignoring volume change event because the car isn't connected");
-                    return;
-                }
-                mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
-                mAvailableVolumeItems.get(
-                        mVolumeGroupId).carVolumeItem.setProgress(progress);
-                mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "Car is not connected!", e);
+            if (mCarAudioManager == null) {
+                Log.w(TAG, "Ignoring volume change event because the car isn't connected");
+                return;
             }
+            mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
+            mAvailableVolumeItems.get(
+                    mVolumeGroupId).carVolumeItem.setProgress(progress);
+            mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
         }
 
         @Override
diff --git a/packages/CarSystemUI/tests/Android.mk b/packages/CarSystemUI/tests/Android.mk
new file mode 100644
index 0000000..1366568
--- /dev/null
+++ b/packages/CarSystemUI/tests/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
+
+LOCAL_PACKAGE_NAME := CarSystemUITests
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    CarSystemUI-tests
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libdexmakerjvmtiagent \
+    libmultiplejvmtiagentsinterferenceagent
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    telephony-common \
+    android.test.base \
+
+LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
+
+# sign this with platform cert, so this test is allowed to inject key events into
+# UI it doesn't own. This is necessary to allow screenshots to be taken
+LOCAL_CERTIFICATE := platform
+
+# Provide jack a list of classes to exclude from code coverage.
+# This is needed because the CarSystemUITests compile CarSystemUI source directly, rather than using
+# LOCAL_INSTRUMENTATION_FOR := CarSystemUI.
+#
+# We want to exclude the test classes from code coverage measurements, but they share the same
+# package as the rest of SystemUI so they can't be easily filtered by package name.
+#
+# Generate a comma separated list of patterns based on the test source files under src/
+# SystemUI classes are in ../src/ so they won't be excluded.
+# Example:
+#   Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
+#   Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
+
+# Filter all src files under src/ to just java files
+local_java_files := $(filter %.java,$(call all-java-files-under, src))
+
+# Transform java file names into full class names.
+# This only works if the class name matches the file name and the directory structure
+# matches the package.
+local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
+local_comma := ,
+local_empty :=
+local_space := $(local_empty) $(local_empty)
+
+# Convert class name list to jacoco exclude list
+# This appends a * to all classes and replace the space separators with commas.
+jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
+
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
+
+ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
+    include $(BUILD_PACKAGE)
+endif
+
+# Reset variables
+local_java_files :=
+local_classes :=
+local_comma :=
+local_space :=
+jacoco_exclude :=
diff --git a/packages/CarSystemUI/tests/AndroidManifest.xml b/packages/CarSystemUI/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a74bb56
--- /dev/null
+++ b/packages/CarSystemUI/tests/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          android:sharedUserId="android.uid.system"
+          package="com.android.systemui.tests">
+
+    <application android:debuggable="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner" />
+
+        <provider
+            android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
+            tools:replace="android:authorities"
+            android:authorities="${applicationId}.lifecycle-tests"
+            android:exported="false"
+            android:enabled="false"
+            android:multiprocess="true" />
+    </application>
+
+    <instrumentation android:name="android.testing.TestableInstrumentation"
+        android:targetPackage="com.android.systemui.tests"
+        android:label="Tests for CarSystemUI">
+    </instrumentation>
+</manifest>
diff --git a/packages/CarSystemUI/tests/AndroidTest.xml b/packages/CarSystemUI/tests/AndroidTest.xml
new file mode 100644
index 0000000..8685632
--- /dev/null
+++ b/packages/CarSystemUI/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Tests for CarSystemUI.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="CarSystemUITests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="CarSystemUITests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.systemui.tests" />
+        <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/packages/CarSystemUI/tests/res/values/config.xml b/packages/CarSystemUI/tests/res/values/config.xml
new file mode 100644
index 0000000..0d08ac2
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/values/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <!-- Configure which system ui bars should be displayed.
+         These can be overwritten within the tests. -->
+    <bool name="config_enableLeftNavigationBar">false</bool>
+    <bool name="config_enableRightNavigationBar">false</bool>
+    <bool name="config_enableBottomNavigationBar">false</bool>
+</resources>
diff --git a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
new file mode 100644
index 0000000..fe59cbf
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.internal.runner.ClassPathScanner;
+import androidx.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
+import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
+
+import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * This is named AAAPlusPlusVerifySysuiRequiredTestPropertiesTest for two reasons.
+ * a) Its so awesome it deserves an AAA++
+ * b) It should run first to draw attention to itself.
+ *
+ * For trues though: this test verifies that all the sysui tests extend the right classes.
+ * This matters because including tests with different context implementations in the same
+ * test suite causes errors, such as the incorrect settings provider being cached.
+ * For an example, see {@link com.android.systemui.DependencyTest}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
+
+    private static final String TAG = "AAA++VerifyTest";
+
+    private static final Class[] BASE_CLS_WHITELIST = {
+            SysuiTestCase.class,
+            SysuiBaseFragmentTest.class,
+    };
+
+    private static final Class[] SUPPORTED_SIZES = {
+            SmallTest.class,
+            MediumTest.class,
+            LargeTest.class,
+            android.test.suitebuilder.annotation.SmallTest.class,
+            android.test.suitebuilder.annotation.MediumTest.class,
+            android.test.suitebuilder.annotation.LargeTest.class,
+    };
+
+    @Test
+    public void testAllClassInheritance() throws Throwable {
+        ArrayList<String> fails = new ArrayList<>();
+        for (String className : getClassNamesFromClassPath()) {
+            Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader());
+            if (!isTestClass(cls)) continue;
+
+            boolean hasParent = false;
+            for (Class<?> parent : BASE_CLS_WHITELIST) {
+                if (parent.isAssignableFrom(cls)) {
+                    hasParent = true;
+                    break;
+                }
+            }
+            boolean hasSize = hasSize(cls);
+            if (!hasSize) {
+                fails.add(cls.getName() + " does not have size annotation, such as @SmallTest");
+            }
+            if (!hasParent) {
+                fails.add(cls.getName() + " does not extend any of " + getClsStr());
+            }
+        }
+
+        assertThat("All sysui test classes must have size and extend one of " + getClsStr(),
+                fails, is(empty()));
+    }
+
+    private boolean hasSize(Class<?> cls) {
+        for (int i = 0; i < SUPPORTED_SIZES.length; i++) {
+            if (cls.getDeclaredAnnotation(SUPPORTED_SIZES[i]) != null) return true;
+        }
+        return false;
+    }
+
+    private Collection<String> getClassNamesFromClassPath() {
+        ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
+
+        ChainedClassNameFilter filter = new ChainedClassNameFilter();
+
+        filter.add(new ExternalClassNameFilter());
+        filter.add(s -> s.startsWith("com.android.systemui")
+                || s.startsWith("com.android.keyguard"));
+
+        try {
+            return scanner.getClassPathEntries(filter);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to scan classes", e);
+        }
+        return Collections.emptyList();
+    }
+
+    private String getClsStr() {
+        return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
+                .stream().map(cls -> cls.getSimpleName()).toArray());
+    }
+
+    /**
+     * Determines if given class is a valid test class.
+     *
+     * @return <code>true</code> if loadedClass is a test
+     */
+    private boolean isTestClass(Class<?> loadedClass) {
+        try {
+            if (Modifier.isAbstract(loadedClass.getModifiers())) {
+                logDebug(String.format("Skipping abstract class %s: not a test",
+                        loadedClass.getName()));
+                return false;
+            }
+            // TODO: try to find upstream junit calls to replace these checks
+            if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
+                // ensure that if a TestCase, it has at least one test method otherwise
+                // TestSuite will throw error
+                if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
+                    return hasJUnit3TestMethod(loadedClass);
+                }
+                return true;
+            }
+            // TODO: look for a 'suite' method?
+            if (loadedClass.isAnnotationPresent(RunWith.class)) {
+                return true;
+            }
+            for (Method testMethod : loadedClass.getMethods()) {
+                if (testMethod.isAnnotationPresent(Test.class)) {
+                    return true;
+                }
+            }
+            logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
+            return false;
+        } catch (Exception e) {
+            // Defensively catch exceptions - Will throw runtime exception if it cannot load
+            // methods.
+            // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
+            // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
+            // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
+            // generic catch all.
+            // For ICS+, Dalvik will throw a NoClassDefFoundException.
+            Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        } catch (Error e) {
+            // defensively catch Errors too
+            Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        }
+    }
+
+    private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
+        for (Method testMethod : loadedClass.getMethods()) {
+            if (isPublicTestMethod(testMethod)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isPublicTestMethod(Method m) {
+        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isTestMethod(Method m) {
+        return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
+                && m.getReturnType().equals(Void.TYPE);
+    }
+
+    /**
+     * Utility method for logging debug messages. Only actually logs a message if TAG is marked
+     * as loggable to limit log spam during normal use.
+     */
+    private void logDebug(String msg) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
new file mode 100644
index 0000000..901d200
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import dagger.Lazy;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class CarNavigationBarControllerTest extends SysuiTestCase {
+
+    private CarNavigationBarController mCarNavigationBar;
+    private NavigationBarViewFactory mNavigationBarViewFactory;
+    private Lazy<HvacController> mHvacControllerLazy;
+    private TestableResources mTestableResources;
+
+    @Mock
+    private HvacController mHvacController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mNavigationBarViewFactory = new NavigationBarViewFactory(mContext);
+        mHvacControllerLazy = () -> mHvacController;
+        mTestableResources = mContext.getOrCreateTestableResources();
+
+        // Needed to inflate top navigation bar.
+        mDependency.injectMockDependency(DarkIconDispatcher.class);
+        mDependency.injectMockDependency(StatusBarIconController.class);
+    }
+
+    @Test
+    public void testConnectToHvac_callsConnect() {
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.connectToHvac();
+
+        verify(mHvacController).connectToCarService();
+    }
+
+    @Test
+    public void testRemoveAllFromHvac_callsRemoveAll() {
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.removeAllFromHvac();
+
+        verify(mHvacController).removeAllComponents();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getBottomWindow();
+        ViewGroup window2 = mCarNavigationBar.getBottomWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testGetLeftWindow_leftDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetLeftWindow_leftEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getLeftWindow();
+        ViewGroup window2 = mCarNavigationBar.getLeftWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testGetRightWindow_rightDisabled_returnsNull() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+
+        assertThat(window).isNull();
+    }
+
+    @Test
+    public void testGetRightWindow_rightEnabled_returnsWindow() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+
+        assertThat(window).isNotNull();
+    }
+
+    @Test
+    public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window1 = mCarNavigationBar.getRightWindow();
+        ViewGroup window2 = mCarNavigationBar.getRightWindow();
+
+        assertThat(window1).isEqualTo(window2);
+    }
+
+    @Test
+    public void testSetBottomWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+        mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetBottomWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getBottomWindow();
+        mCarNavigationBar.setBottomWindowVisibility(View.GONE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testSetLeftWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetLeftWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getLeftWindow();
+        mCarNavigationBar.setLeftWindowVisibility(View.GONE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testSetRightWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+        mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetRightWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        ViewGroup window = mCarNavigationBar.getRightWindow();
+        mCarNavigationBar.setRightWindowVisibility(View.GONE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
+        assertThat(controller).isNull();
+        mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
+        controller = bottomBar.getStatusBarWindowTouchListener();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationBarController.NotificationsShadeController controller =
+                bottomBar.getNotificationsPanelController();
+        assertThat(controller).isNull();
+        mCarNavigationBar.registerNotificationController(
+                mock(CarNavigationBarController.NotificationsShadeController.class));
+        controller = bottomBar.getNotificationsPanelController();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+
+        mCarNavigationBar.registerNotificationController(
+                mock(CarNavigationBarController.NotificationsShadeController.class));
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationBarController.NotificationsShadeController controller =
+                bottomBar.getNotificationsPanelController();
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
+
+        mCarNavigationBar.showAllKeyguardButtons(/* isSetUp= */ true);
+
+        assertThat(bottomKeyguardButtons.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
+
+        mCarNavigationBar.showAllKeyguardButtons(/* isSetUp= */ true);
+
+        assertThat(bottomButtons.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
+
+        mCarNavigationBar.showAllKeyguardButtons(/* isSetUp= */ true);
+        assertThat(bottomKeyguardButtons.getVisibility()).isEqualTo(View.VISIBLE);
+        mCarNavigationBar.hideAllKeyguardButtons(/* isSetUp= */ true);
+
+        assertThat(bottomKeyguardButtons.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
+
+        mCarNavigationBar.showAllKeyguardButtons(/* isSetUp= */ true);
+        assertThat(bottomButtons.getVisibility()).isEqualTo(View.GONE);
+        mCarNavigationBar.hideAllKeyguardButtons(/* isSetUp= */ true);
+
+        assertThat(bottomButtons.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
+
+        boolean hasUnseen = true;
+        mCarNavigationBar.toggleAllNotificationsUnseenIndicator(/* isSetUp= */ true,
+                hasUnseen);
+
+        assertThat(notifications.getUnseen()).isTrue();
+    }
+
+    @Test
+    public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+                mHvacControllerLazy);
+        CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
+        CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
+
+        boolean hasUnseen = false;
+        mCarNavigationBar.toggleAllNotificationsUnseenIndicator(/* isSetUp= */ true,
+                hasUnseen);
+
+        assertThat(notifications.getUnseen()).isFalse();
+    }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 48d34ae..af96982 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -129,17 +129,17 @@
     }
 
     @Override
-    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         enforceShellRestrictions();
-        return super.enforceReadPermissionInner(uri, callingPkg, callerToken);
+        return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
     }
 
     @Override
-    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
-            throws SecurityException {
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+            @Nullable String featureId, IBinder callerToken) throws SecurityException {
         enforceShellRestrictions();
-        return super.enforceWritePermissionInner(uri, callingPkg, callerToken);
+        return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
     }
 
     public void updateVolumes() {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index c4df2e8..b9daf7f 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -406,8 +406,8 @@
             return null;
         }
         try {
-            return provider.call(context.getPackageName(), uri.getAuthority(),
-                    method, uri.toString(), bundle);
+            return provider.call(context.getPackageName(), context.getFeatureId(),
+                    uri.getAuthority(), method, uri.toString(), bundle);
         } catch (RemoteException e) {
             return null;
         }
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 245ca14..f25b5eb 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Beskikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tik om aan te meld"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Gekoppel, geen internet nie"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Daar kan nie by private DNS-bediener ingegaan word nie"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Geen internet nie"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Aanmelding word vereis"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index bfd3196..6332c848 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"በ%1$s በኩል የሚገኝ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ለመመዝገብ መታ ያድርጉ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ተገናኝቷል፣ ምንም በይነመረብ የለም"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"የተገደበ ግንኙነት"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ምንም በይነመረብ የለም"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ወደ መለያ መግባት ያስፈልጋል"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 8b67b0b..8c72527 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"‏متوفرة عبر %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"انقر للاشتراك."</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"متصلة ولكن بلا إنترنت"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"لا يتوفر اتصال إنترنت."</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"يلزم تسجيل الدخول"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index f3ca337..a7ea1e0 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ছাইন আপ কৰিবলৈ টিপক"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"সংযোজিত, ইণ্টাৰনেট নাই"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ইণ্টাৰনেট সংযোগ সীমিত"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ইণ্টাৰনেট সংযোগ নাই"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ছাইন ইন কৰা দৰকাৰী"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index d35dfe8..cb7db78 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s vasitəsilə əlçatandır"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Qeydiyyatdan keçmək üçün klikləyin"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Qoşuludur, internet yoxdur"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Özəl DNS serverinə giriş mümkün deyil"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Məhdud bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"İnternet yoxdur"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Giriş tələb olunur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 448de4b..75feb32 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dostupna je preko pristupne tačke %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Dodirnite da biste se registrovali"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Veza je uspostavljena, nema interneta"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Pristup privatnom DNS serveru nije uspeo"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nema interneta"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Treba da se prijavite"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d68c0f3..677aa24 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Даступна праз %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Націсніце, каб зарэгістравацца"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Падключана, без доступу да інтэрнэту"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Абмежаваныя магчымасці падключэння"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Не падключана да інтэрнэту"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Трэба выканаць уваход"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index cb99f64..1620422 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Мрежата е достъпна през „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Докоснете, за да се регистрирате"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Установена е връзка – няма достъп до интернет"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Не може да се осъществи достъп до частния DNS сървър"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ограничена връзка"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Няма връзка с интернет"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Изисква се вход в профила"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index f2f4f52..b1e37a6 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s এর মাধ্যমে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"সাইন-আপ করতে ট্যাপ করুন"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"কানেক্ট, ইন্টারনেট নেই"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"সীমিত কানেকশন"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ইন্টারনেট কানেকশন নেই"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"সাইন-ইন করা দরকার"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 45b8dd9..911a831 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dostupan preko %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Dodirnite za prijavu"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Povezano, nema interneta"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Nije moguće pristupiti privatnom DNS serveru"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nema internetske veze"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Potrebna je prijava"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index b624df0..58c2b67 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponible mitjançant %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toca per registrar-te"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connectada, sense Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"No es pot accedir al servidor DNS privat"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Connexió limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sense connexió a Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Cal iniciar la sessió"</string>
@@ -237,7 +238,7 @@
     <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="7234956835280563341">"Activa el còdec d\'àudio per Bluetooth\nSelecció: mode de canal"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Còdec LDAC d\'àudio per Bluetooth: qualitat de reproducció"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="6893955536658137179">"Activa l\'LDAC d\'àudio per Bluetooth\nSelecció de còdec: qualitat de reproducció"</string>
-    <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"S\'està reproduint en temps real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Reproducció en continu: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS privat"</string>
     <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Selecciona el mode de DNS privat"</string>
     <string name="private_dns_mode_off" msgid="8236575187318721684">"Desactivat"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 042e12a..3c3d5b8 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dostupné prostřednictvím %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Klepnutím se zaregistrujete"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Připojeno, není k dispozici internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Nelze získat přístup k soukromému serveru DNS"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Omezené připojení"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nejste připojeni k internetu"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Je vyžadováno přihlášení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 4723293..bf5d6cf 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tilgængelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tryk for at registrere dig"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tilsluttet – intet internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Der er ikke adgang til den private DNS-server"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Begrænset forbindelse"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Intet internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Login er påkrævet"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 4f5a965..a6dbd5a 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Verfügbar über %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Zum Anmelden tippen"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Verbunden, kein Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Eingeschränkte Verbindung"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Kein Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Anmeldung erforderlich"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 753dea8..fdba74a 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Διαθέσιμο μέσω %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Πατήστε για εγγραφή"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Συνδέθηκε, χωρίς σύνδεση στο διαδίκτυο"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Περιορισμένη σύνδεση"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Απαιτείται σύνδεση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index dd3d278..581adf8 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tap to sign up"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connected, no Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"No Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Sign-in required"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index dd3d278..581adf8 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tap to sign up"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connected, no Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"No Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Sign-in required"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index dd3d278..581adf8 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tap to sign up"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connected, no Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"No Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Sign-in required"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index dd3d278..581adf8 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tap to sign up"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connected, no Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"No Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Sign-in required"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index d9f61d8..e75d7bc 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎Available via %1$s‎‏‎‎‏‎"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎Tap to sign up‎‏‎‎‏‎"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‎Connected, no internet‎‏‎‎‏‎"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎Private DNS server cannot be accessed‎‏‎‎‏‎"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎Limited connection‎‏‎‎‏‎"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎No internet‎‏‎‎‏‎"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎Sign in required‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 30cb0a1..97cce55 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Presiona para registrarte"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conectado pero sin conexión a Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"No se puede acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sin Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Acceso obligatorio"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 32905df..7ba1a94 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toca para registrarte"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conexión sin Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"No se ha podido acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sin Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Debes iniciar sesión"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 79b8a84..0e98752 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Saadaval üksuse %1$s kaudu"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Puudutage registreerumiseks"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Ühendatud, Interneti-ühendus puudub"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Privaatsele DNS-serverile ei pääse juurde"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Piiratud ühendus"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Interneti-ühendus puudub"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Nõutav on sisselogimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 38ae9c2..872e9a5 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s bidez erabilgarri"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Sakatu erregistratzeko"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Konektatuta; ezin da atzitu Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Ezin da atzitu DNS zerbitzari pribatua"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Konexio mugatua"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ez dago Interneteko konexiorik"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Saioa hasi behar da"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index a883d6cd..6e281fe 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"‏در دسترس از طریق %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"برای ثبت‌نام ضربه بزنید"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"متصل، بدون اینترنت"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"‏سرور DNS خصوصی قابل دسترسی نیست"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"عدم دسترسی به اینترنت"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ورود به سیستم لازم است"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 929e101..8c3630a 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Käytettävissä seuraavan kautta: %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Rekisteröidy napauttamalla"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Yhdistetty, ei internetyhteyttä"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Ei pääsyä yksityiselle DNS-palvelimelle"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Rajallinen yhteys"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ei internetyhteyttä"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Sisäänkirjautuminen vaaditaan"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index d3bfc96..6c5834a 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Accessible par %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toucher pour vous connecter"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connecté, aucun accès à Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Aucune connexion Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Connexion requise"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index b5706ce..508556e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponible via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Appuyez ici pour vous connecter"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connecté, aucun accès à Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Aucun accès à Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Connexion requise"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 2aa1604..1a3ae3d 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dispoñible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toca para rexistrarte"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conexión sen Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Non se puido acceder ao servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Pouca conexión"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Non hai conexión a Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"É obrigatorio iniciar sesión"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index c2cc4c5..b3f5185 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s દ્વારા ઉપલબ્ધ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"સાઇન અપ કરવા માટે ટૅપ કરો"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"કનેક્ટ કર્યું, કોઈ ઇન્ટરનેટ નથી"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"મર્યાદિત કનેક્શન"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ઇન્ટરનેટ ઍક્સેસ નથી"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"સાઇન ઇન આવશ્યક"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 66c7a16..454773c 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s के द्वारा उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"साइन अप करने के लिए टैप करें"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"कनेक्ट हो गया है, लेकिन इंटरनेट नहीं है"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"सीमित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"इंटरनेट कनेक्शन नहीं है"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"साइन इन करना ज़रूरी है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 0fc16f8..0ec154b 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dostupno putem %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Dodirnite da biste se registrirali"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Povezano, bez interneta"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Nije moguće pristupiti privatnom DNS poslužitelju"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nema interneta"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Obavezna prijava"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 1388dbb..4a0af7d 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Elérhető a következőn keresztül: %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Koppintson a regisztrációhoz"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Csatlakozva, nincs internet-hozzáférés"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Korlátozott kapcsolat"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nincs internetkapcsolat"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Bejelentkezést igényel"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 2145adb..cc0ecb8 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Հասանելի է %1$s-ի միջոցով"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Հպեք՝ գրանցվելու համար"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Միացված է, սակայն ինտերնետ կապ չկա"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Մասնավոր DNS սերվերն անհասանելի է"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Սահմանափակ կապ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ինտերնետ կապ չկա"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Անհրաժեշտ է մուտք գործել"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 7ebe6b7..0733c1f 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Ketuk untuk mendaftar"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tersambung, tidak ada internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Server DNS pribadi tidak dapat diakses"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Koneksi terbatas"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Tidak ada internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Perlu login"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index eede117..28ed8fc 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Í boði í gegnum %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Ýttu til að skrá þig"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tengt, enginn netaðgangur"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Ekki næst í DNS-einkaþjón"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Takmörkuð tenging"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Engin nettenging"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Innskráningar krafist"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 2c64ec2..b0569b0 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponibile tramite %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tocca per registrarti"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Connesso, senza Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Non è possibile accedere al server DNS privato"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Connessione limitata"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nessuna connessione a Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Accesso richiesto"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 30d6a4a..f7d4fcd 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"‏זמינה דרך %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"יש להקיש כדי להירשם"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"מחובר. אין אינטרנט"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"חיבור מוגבל"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"אין אינטרנט"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"נדרשת כניסה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 6cb5514..4d5c86c 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s経由で使用可能"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"タップして登録してください"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"接続済み、インターネット接続なし"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"プライベート DNS サーバーにアクセスできません"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"接続が制限されています"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"インターネット未接続"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ログインが必要"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 4c78a38..20342bc 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"ხელმისაწვდომია %1$s-ით"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"შეეხეთ რეგისტრაციისთვის"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"დაკავშირებულია, ინტერნეტის გარეშე"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"შეზღუდული კავშირი"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ინტერნეტ-კავშირი არ არის"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"აუცილებელია სისტემაში შესვლა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index ce08657..7b6d29e 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s арқылы қолжетімді"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Тіркелу үшін түртіңіз."</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Қосылған, интернет жоқ"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Жеке DNS серверіне кіру мүмкін емес."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Шектеулі байланыс"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Интернетпен байланыс жоқ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Есептік жазбаға кіру керек"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 43c9282..115be8e 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"មានតាមរយៈ %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ចុច​ដើម្បី​ចុះឈ្មោះ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"បាន​ភ្ជាប់ ប៉ុន្តែ​គ្មាន​អ៊ីនធឺណិត​ទេ"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"មិនអាច​ចូលប្រើ​ម៉ាស៊ីនមេ DNS ឯកជន​បានទេ"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ការតភ្ជាប់មានកម្រិត"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"គ្មាន​អ៊ីនធឺណិតទេ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"តម្រូវ​ឱ្យ​ចូល​គណនី"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 253104b..ac8bfb1 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್‌ ಮಾಡಿ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ, ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ಸೈನ್ ಇನ್ ಮಾಡುವ ಅಗತ್ಯವಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index ac44c0d..4e54310 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s을(를) 통해 사용 가능"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"탭하여 가입"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"연결됨, 인터넷 사용 불가"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"비공개 DNS 서버에 액세스할 수 없습니다."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"제한된 연결"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"인터넷 연결 없음"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"로그인 필요"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index dd1ff30..e891b5a 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s аркылуу жеткиликтүү"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Катталуу үчүн таптап коюңуз"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Туташып турат, Интернет жок"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Жеке DNS сервери жеткиликсиз"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Байланыш чектелген"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Интернет жок"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Аккаунтка кирүү талап кылынат"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 28e8111..406a42b 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"ມີ​ໃຫ້​ຜ່ານ %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ແຕະເພື່ອສະໝັກ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ມີອິນເຕີເນັດ"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ບໍ່ມີອິນເຕີເນັດ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ຈຳເປັນຕ້ອງເຂົ້າສູ່ລະບົບ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 93fcaa7..b305fd90 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Pasiekiama naudojant „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Palieskite, kad prisiregistruotumėte"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Prisijungta, nėra interneto"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Privataus DNS serverio negalima pasiekti"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ribotas ryšys"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nėra interneto ryšio"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Reikia prisijungti"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c12d097..c9e2c11 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Pieejams, izmantojot %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Pieskarieties, lai reģistrētos"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Savienojums izveidots, nav piekļuves internetam"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Nevar piekļūt privātam DNS serverim."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ierobežots savienojums"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nav piekļuves internetam"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Nepieciešama pierakstīšanās"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 9a76a0a..e06d414 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Достапно преку %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Допрете за да се регистрирате"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Поврзана, нема интернет"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Не може да се пристапи до приватниот DNS-сервер"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ограничена врска"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Нема интернет"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Потребно е најавување"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index bb76295..36e632a 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s വഴി ലഭ്യം"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"കണക്റ്റ് ചെയ്‌തു, ഇന്റർനെറ്റ് ഇല്ല"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"സ്വകാര്യ DNS സെർവർ ആക്‌സസ് ചെയ്യാനാവില്ല"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"പരിമിത കണക്‌ഷൻ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ഇന്റർനെറ്റ് ഇല്ല"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"സൈൻ ഇൻ ചെയ്യേണ്ടത് ആവശ്യമാണ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 65a8ca6..1a5a0af 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s-р боломжтой"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Бүртгүүлэхийн тулд товшино уу"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Холбогдсон хэдий ч интернет алга"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Хувийн DNS серверт хандах боломжгүй байна"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Хязгаарлагдмал холболт"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Интернэт алга"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Нэвтрэх шаардлагатай"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 3d75ad6..7510107 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s द्वारे उपलब्‍ध"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"साइन अप करण्यासाठी टॅप करा"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"कनेक्‍ट केले, इंटरनेट नाही"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"मर्यादित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"इंटरनेट नाही"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"साइन इन करणे आवश्यक आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 0b2a4b0..82bd697 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Ketik untuk daftar"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Disambungkan, tiada Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Pelayan DNS peribadi tidak boleh diakses"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Sambungan terhad"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Tiada Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Log masuk diperlukan"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 6fde69a..9636f06 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"အကောင့်ဖွင့်ရန် တို့ပါ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ချိတ်ဆက်ထားသည်၊ အင်တာနက်မရှိ"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"အင်တာနက် မရှိပါ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"လက်မှတ်ထိုးဝင်ရန် လိုအပ်သည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 66ad20e..99af7c8 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tilgjengelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Trykk for å registrere deg"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tilkoblet – ingen Internett-tilgang"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Den private DNS-tjeneren kan ikke nås"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Begrenset tilkobling"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ingen internettilkobling"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Pålogging kreves"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 860e9bf..cdf2c7d 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s मार्फत उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"साइन अप गर्न ट्याप गर्नुहोस्"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"जडान गरियो तर इन्टरनेट छैन"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"सीमित जडान"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"इन्टरनेटमाथिको पहुँच छैन"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"साइन इन गर्न आवश्यक छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2fff3283..709e98d 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Beschikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tik om aan te melden"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Verbonden, geen internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Geen toegang tot privé-DNS-server"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Geen internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Inloggen vereist"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index ff2a534..abe0198 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ସଂଯୁକ୍ତ, ଇଣ୍ଟର୍‌ନେଟ୍‌ ନାହିଁ"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ସୀମିତ ସଂଯୋଗ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"କୌଣସି ଇଣ୍ଟରନେଟ୍‌ ନାହିଁ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ସାଇନ୍-ଇନ୍ ଆବଶ୍ୟକ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 0cf95753..b372185 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"ਕਨੈਕਟ ਕੀਤਾ, ਕੋਈ ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ਸਾਈਨ-ਇਨ ਲੋੜੀਂਦਾ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index caed8c3..e7a8f22 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Dostępne przez %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Kliknij, by się zarejestrować"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Połączono, brak internetu"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ograniczone połączenie"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Brak internetu"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Musisz się zalogować"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 04a24f1..6e75fba 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toque para se inscrever"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conectada, sem Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sem Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"É necessário fazer login"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index a206d1a..4ee0a17 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponível através de %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toque para se inscrever"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Ligado, sem Internet."</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Não é possível aceder ao servidor DNS."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ligação limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sem Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"É necessário iniciar sessão"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 04a24f1..6e75fba 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Toque para se inscrever"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conectada, sem Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Sem Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"É necessário fazer login"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 76a56e5..c5725d3 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Disponibilă prin %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Atingeți pentru a vă înscrie"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Conectată, fără internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Serverul DNS privat nu poate fi accesat"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Conexiune limitată"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Fără conexiune la internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Trebuie să vă conectați"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 6e1d29a..6e98601 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Доступно через %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Нажмите, чтобы зарегистрироваться"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Подключено, без доступа к Интернету"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Доступа к частному DNS-серверу нет."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Подключение к сети ограничено."</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Нет подключения к Интернету"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Требуется выполнить вход."</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 186c23a..0f67a65 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s හරහා ලබා ගැනීමට හැකිය"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"සම්බන්ධයි, අන්තර්ජාලය නැත"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"පුද්ගලික DNS සේවාදායකයට ප්‍රවේශ වීමට නොහැකිය"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"සීමිත සම්බන්ධතාව"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"අන්තර්ජාලය නැත"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"පිරීම අවශ්‍යයි"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 9559975..e4b4848 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"K dispozícii prostredníctvom %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Prihláste sa klepnutím"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Pripojené, žiadny internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Obmedzené pripojenie"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Žiadny internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Vyžaduje sa prihlásenie"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 62e4ad1..3e181c2 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Na voljo prek: %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Dotaknite se, če se želite registrirati"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Vzpostavljena povezava, brez interneta"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Do zasebnega strežnika DNS ni mogoče dostopati"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Omejena povezava"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Brez internetne povezave"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Zahtevana je prijava"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 0498f31..5380302 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"E mundshme përmes %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Trokit për t\'u regjistruar"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"U lidh, por nuk ka internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Serveri privat DNS nuk mund të qaset"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Lidhje e kufizuar"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Nuk ka internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Kërkohet identifikimi"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 3157dee..d68c9e8 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Доступна је преко приступне тачке %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Додирните да бисте се регистровали"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Веза је успостављена, нема интернета"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Приступ приватном DNS серверу није успео"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Ограничена веза"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Нема интернета"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Треба да се пријавите"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 31517b8..45841f0 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tillgängligt via %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Tryck för att logga in"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Ansluten, inget internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Det går inte att komma åt den privata DNS-servern."</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Begränsad anslutning"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Inget internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Inloggning krävs"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index d73084f..a8e73d7 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Inapatikana kupitia %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Gusa ili ujisajili"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Imeunganishwa, hakuna intaneti"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Seva ya faragha ya DNS haiwezi kufikiwa"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Muunganisho hafifu"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Hakuna intaneti"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Unahitaji kuingia katika akaunti"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index de70d08..83fe954 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s வழியாகக் கிடைக்கிறது"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"பதிவு செய்யத் தட்டவும்"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"இணைக்கப்பட்டுள்ளது, ஆனால் இண்டர்நெட் இல்லை"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"இணைய இணைப்பு இல்லை"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"உள்நுழைய வேண்டும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 03ae94f..ad0f673 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ద్వారా అందుబాటులో ఉంది"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"సైన్ అప్ చేయడానికి నొక్కండి"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"కనెక్ట్ చేయబడింది, ఇంటర్నెట్ లేదు"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"ప్రైవేట్ DNS సర్వర్‌ను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"పరిమిత కనెక్షన్"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ఇంటర్నెట్ లేదు"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"సైన్ ఇన్ చేయాలి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 1cfe45e..c9e232d 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"พร้อมใช้งานผ่านทาง %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"แตะเพื่อลงชื่อสมัครใช้"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"เชื่อมต่อแล้ว ไม่พบอินเทอร์เน็ต"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"การเชื่อมต่อที่จำกัด"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"ไม่มีอินเทอร์เน็ต"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"ต้องลงชื่อเข้าใช้"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 8b3118b..74307f8 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Available sa pamamagitan ng %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"I-tap para mag-sign up"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Nakakonekta, walang internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Hindi ma-access ang pribadong DNS server"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Limitadong koneksyon"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Walang internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Kinakailangang mag-sign in"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 5891c79..b95d941 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s üzerinden kullanılabilir"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Kaydolmak için dokunun"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Bağlı, internet yok"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Gizli DNS sunucusuna erişilemiyor"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Sınırlı bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"İnternet yok"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Oturum açılması gerekiyor"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 0bca307..61a0c3e 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Доступ через %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Торкніться, щоб увійти"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Під’єднано, але немає доступу до Інтернету"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Немає доступу до приватного DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Обмежене з’єднання"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Немає Інтернету"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Потрібно ввійти в обліковий запис"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 1e27b48..f21e891 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -43,6 +43,8 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"‏دستیاب بذریعہ ‎%1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"سائن اپ کے لیے تھپتھپائیں"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"منسلک، انٹرنیٹ نہیں ہے"</string>
+    <!-- no translation found for private_dns_broken (7356676011023412490) -->
+    <skip />
     <string name="wifi_limited_connection" msgid="7717855024753201527">"محدود کنکشن"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"انٹرنیٹ نہیں ہے"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"سائن ان درکار ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 487100c..a0a79e3 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"%1$s orqali ishlaydi"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Yozilish uchun bosing"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Ulangan, lekin internet aloqasi yo‘q"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Xususiy DNS server ishlamayapti"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Cheklangan aloqa"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Internet yo‘q"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Hisob bilan kirish zarur"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index c247617..3723b83 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Có sẵn qua %1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Nhấn để đăng ký"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Đã kết nối, không có Internet"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Không thể truy cập máy chủ DNS riêng tư"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Kết nối giới hạn"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Không có Internet"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Yêu cầu đăng nhập"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index dddc107..f1200ee 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"可通过%1$s连接"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"点按即可注册"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"已连接,但无法访问互联网"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"无法访问私人 DNS 服务器"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"网络连接受限"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"无法访问互联网"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"必须登录"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 8560e22..57ab472 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"可透過 %1$s 連線"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"輕按即可登入"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"已連線,但沒有互聯網"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"連線受限"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"沒有互聯網連線"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"必須登入"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 404aa19..630619b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"可透過 %1$s 使用"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"輕觸即可註冊"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"已連線,沒有網際網路"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"連線能力受限"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"沒有網際網路連線"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"必須登入"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 542332f..ede336e 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -43,6 +43,7 @@
     <string name="available_via_passpoint" msgid="1617440946846329613">"Iyatholakala nge-%1$s"</string>
     <string name="tap_to_sign_up" msgid="6449724763052579434">"Thepha ukuze ubhalisele"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Kuxhunyiwe, ayikho i-inthanethi"</string>
+    <string name="private_dns_broken" msgid="7356676011023412490">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string>
     <string name="wifi_limited_connection" msgid="7717855024753201527">"Iqoqo elikhawulelwe"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ayikho i-inthanethi"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Ukungena ngemvume kuyadingeka"</string>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f7fc0c5..0c3254a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -228,5 +228,6 @@
         VALIDATORS.put(Secure.GLOBAL_ACTIONS_PANEL_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.AWARE_LOCK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 8fb879d..1e75fe7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -248,7 +248,7 @@
                 Bundle args = new Bundle();
                 args.putInt(Settings.CALL_METHOD_USER_KEY,
                         ActivityManager.getService().getCurrentUser().id);
-                Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
                 success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
             } catch (RemoteException e) {
@@ -264,7 +264,7 @@
                 Bundle args = new Bundle();
                 args.putInt(Settings.CALL_METHOD_USER_KEY,
                         ActivityManager.getService().getCurrentUser().id);
-                Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         Settings.CALL_METHOD_LIST_CONFIG, null, args);
                 if (b != null) {
                     Map<String, String> flagsToValues =
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 80faf476..fdc987f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1080,6 +1080,9 @@
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
         }
 
+        DeviceConfig.enforceReadPermission(getContext(),
+                prefix != null ? prefix.split("/")[0] : null);
+
         synchronized (mLock) {
             // Get the settings.
             SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index 36360a3..3b3ca5b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -309,7 +309,7 @@
             try {
                 Bundle arg = new Bundle();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-                Bundle result = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                Bundle result = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         callListCommand, null, arg);
                 lines.addAll(result.getStringArrayList(SettingsProvider.RESULT_SETTINGS_LIST));
                 Collections.sort(lines);
@@ -334,7 +334,7 @@
             try {
                 Bundle arg = new Bundle();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-                Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         callGetCommand, key, arg);
                 if (b != null) {
                     result = b.getPairValue();
@@ -372,7 +372,7 @@
                 if (makeDefault) {
                     arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
-                provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         callPutCommand, key, arg);
             } catch (RemoteException e) {
                 throw new RuntimeException("Failed in IPC", e);
@@ -396,7 +396,7 @@
             try {
                 Bundle arg = new Bundle();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-                Bundle result = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
+                Bundle result = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
                         callDeleteCommand, key, arg);
                 return result.getInt(SettingsProvider.RESULT_ROWS_DELETED);
             } catch (RemoteException e) {
@@ -423,7 +423,7 @@
                 }
                 String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-                provider.call(packageName, Settings.AUTHORITY, callResetCommand, null, arg);
+                provider.call(packageName, null, Settings.AUTHORITY, callResetCommand, null, arg);
             } catch (RemoteException e) {
                 throw new RuntimeException("Failed in IPC", e);
             }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9255c87..10d990a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -732,7 +732,8 @@
                  Settings.Secure.SILENCE_GESTURE,
                  Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
                  Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
-                 Settings.Secure.FACE_UNLOCK_RE_ENROLL);
+                 Settings.Secure.FACE_UNLOCK_RE_ENROLL,
+                 Settings.Secure.TAP_GESTURE);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 3f56ff0..8825f12 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -67,13 +67,11 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (args != null && args.length > 0 && args[0].equals("--config")) {
-            dumpConfig(pw);
-            return;
-        }
-
         dumpServices(((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
-        dumpConfig(pw);
+
+        if (args == null || args.length == 0 || args[0].equals("--config")) {
+            dumpConfig(pw);
+        }
     }
 
     static void dumpServices(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4be610f..61ded13 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -30,7 +30,7 @@
  * Services and Activities that are injectable should go here.
  */
 @Module
-public abstract class ActivityBinder {
+public abstract class DefaultActivityBinder {
     /** Inject into TunerActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java
index 4e4c06e..d8989ee 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java
@@ -16,16 +16,13 @@
 
 package com.android.systemui.dagger;
 
-import dagger.Binds;
 import dagger.Module;
 
 /**
  * Dagger Module that collects related sub-modules together.
+ *
+ * See {@link ContextComponentResolver}
  */
-@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class})
-public abstract class ComponentBinder {
-    /** */
-    @Binds
-    public abstract ContextComponentHelper bindComponentHelper(
-            ContextComponentResolver componentHelper);
+@Module(includes = {DefaultActivityBinder.class, DefaultServiceBinder.class, SystemUIBinder.class})
+public abstract class DefaultComponentBinder {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 1f2c0a1..14bb80c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -31,7 +31,7 @@
  * Services that are injectable should go here.
  */
 @Module
-public abstract class ServiceBinder {
+public abstract class DefaultServiceBinder {
     /** */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6674c12..9032c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.doze.DozeHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
@@ -33,6 +34,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -241,5 +243,10 @@
     /**
      */
     @Binds
-    public abstract FalsingManager provideFalsingmanager(FalsingManagerProxy falsingManagerImpl);
+    public abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+
+    /**
+     */
+    @Binds
+    public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 738f539..27c526b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,6 +24,7 @@
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.volume.VolumeUI;
 
@@ -80,6 +81,12 @@
     @ClassKey(ScreenDecorations.class)
     public abstract SystemUI bindScreenDecorations(ScreenDecorations sysui);
 
+    /** Inject into StatusBar. */
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBar.class)
+    public abstract SystemUI bindsStatusBar(StatusBar sysui);
+
     /** Inject into VolumeUI. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index c95b50b..7b8d3bc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -21,7 +21,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.SystemUI;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.power.EnhancedEstimates;
@@ -39,8 +38,6 @@
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 /**
  * A dagger module for injecting default implementations of components of System UI that may be
@@ -74,11 +71,6 @@
     @Binds
     abstract ShadeController provideShadeController(StatusBar statusBar);
 
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBar.class)
-    public abstract SystemUI providesStatusBar(StatusBar statusBar);
-
     @Singleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4e60f19..ca8e53d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -30,6 +30,7 @@
 
 import javax.inject.Singleton;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -38,9 +39,12 @@
  * implementation.
  */
 @Module(includes = {AssistModule.class,
-                    ComponentBinder.class,
                     PeopleHubModule.class})
 public abstract class SystemUIModule {
+    /** */
+    @Binds
+    public abstract ContextComponentHelper bindComponentHelper(
+            ContextComponentResolver componentHelper);
 
     @Singleton
     @Provides
@@ -56,7 +60,6 @@
                 keyguardUpdateMonitor);
     }
 
-
     @Singleton
     @Provides
     static SysUiState provideSysUiState() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index 113c9c8..83d956c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -37,6 +37,7 @@
  */
 @Singleton
 @Component(modules = {
+        DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
         SystemServicesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 0988e34..d668665 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -163,7 +163,7 @@
                 if (!isPlaybackActive(state.getState())) {
                     clearCurrentMediaNotification();
                 }
-                dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+                findAndUpdateMediaNotifications();
             }
         }
 
@@ -200,6 +200,16 @@
         mEntryManager = notificationEntryManager;
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
+            public void onPendingEntryAdded(NotificationEntry entry) {
+                findAndUpdateMediaNotifications();
+            }
+
+            @Override
+            public void onPreEntryUpdated(NotificationEntry entry) {
+                findAndUpdateMediaNotifications();
+            }
+
+            @Override
             public void onEntryRemoved(
                     NotificationEntry entry,
                     NotificationVisibility visibility,
@@ -272,16 +282,12 @@
         boolean metaDataChanged = false;
 
         synchronized (mEntryManager.getNotificationData()) {
-            ArrayList<NotificationEntry> activeNotifications =
-                    mEntryManager.getNotificationData().getActiveNotifications();
-            final int N = activeNotifications.size();
+            Set<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
 
             // Promote the media notification with a controller in 'playing' state, if any.
             NotificationEntry mediaNotification = null;
             MediaController controller = null;
-            for (int i = 0; i < N; i++) {
-                final NotificationEntry entry = activeNotifications.get(i);
-
+            for (NotificationEntry entry : allNotifications) {
                 if (entry.isMediaNotification()) {
                     final MediaSession.Token token =
                             entry.getSbn().getNotification().extras.getParcelable(
@@ -319,8 +325,7 @@
                             // now to see if we have one like this
                             final String pkg = aController.getPackageName();
 
-                            for (int i = 0; i < N; i++) {
-                                final NotificationEntry entry = activeNotifications.get(i);
+                            for (NotificationEntry entry : allNotifications) {
                                 if (entry.getSbn().getPackageName().equals(pkg)) {
                                     if (DEBUG_MEDIA) {
                                         Log.v(TAG, "DEBUG_MEDIA: found controller matching "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index bde097a..404087d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -53,8 +53,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -561,6 +563,15 @@
     }
 
     /**
+     * @return all notification we're currently aware of (both pending and visible notifications)
+     */
+    public Set<NotificationEntry> getAllNotifs() {
+        Set<NotificationEntry> allNotifs = new HashSet<>(mPendingNotifications.values());
+        allNotifs.addAll(mNotificationData.getActiveNotifications());
+        return allNotifs;
+    }
+
+    /**
      * Gets the pending or visible notification entry with the given key. Returns null if
      * notification doesn't exist.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 033171a..1e8e28f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -55,10 +55,10 @@
 @Singleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
 
-    private static final String TAG = "BiometricUnlockController";
+    private static final String TAG = "BiometricUnlockCtrl";
     private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
-    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 50d33a7..bc48235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -24,7 +24,6 @@
 import android.provider.Settings;
 import android.util.MathUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
@@ -188,12 +187,7 @@
             return;
         }
         mControlScreenOffAnimation = controlScreenOffAnimation;
-        getPowerManager().setDozeAfterScreenOff(!controlScreenOffAnimation);
-    }
-
-    @VisibleForTesting
-    protected PowerManager getPowerManager() {
-        return mPowerManager;
+        mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation);
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index fe3c04e..1ecc489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -29,10 +29,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Controller which handles all the doze animations of the scrims.
  */
+@Singleton
 public class DozeScrimController implements StateListener {
     private static final String TAG = "DozeScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
new file mode 100644
index 0000000..2854355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/**
+ * Implementation of DozeHost for SystemUI.
+ */
+@Singleton
+public final class DozeServiceHost implements DozeHost {
+    private static final String TAG = "DozeServiceHost";
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final DozeLog mDozeLog;
+    private final PowerManager mPowerManager;
+    private boolean mAnimateWakeup;
+    private boolean mAnimateScreenOff;
+    private boolean mIgnoreTouchWhilePulsing;
+    private Runnable mPendingScreenOffCallback;
+    @VisibleForTesting
+    boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
+            "persist.sysui.wake_performs_auth", true);
+    private boolean mDozingRequested;
+    private boolean mDozing;
+    private boolean mPulsing;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+    private final BatteryController mBatteryController;
+    private final ScrimController mScrimController;
+    private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    private BiometricUnlockController mBiometricUnlockController;
+    private final KeyguardViewMediator mKeyguardViewMediator;
+    private final AssistManager mAssistManager;
+    private final DozeScrimController mDozeScrimController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final VisualStabilityManager mVisualStabilityManager;
+    private final PulseExpansionHandler mPulseExpansionHandler;
+    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+    private NotificationIconAreaController mNotificationIconAreaController;
+    private StatusBarWindowViewController mStatusBarWindowViewController;
+    private StatusBarWindowView mStatusBarWindow;
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private NotificationPanelView mNotificationPanel;
+    private View mAmbientIndicationContainer;
+    private StatusBar mStatusBar;
+
+    @Inject
+    public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
+            WakefulnessLifecycle wakefulnessLifecycle,
+            SysuiStatusBarStateController statusBarStateController,
+            DeviceProvisionedController deviceProvisionedController,
+            HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
+            ScrimController scrimController,
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            KeyguardViewMediator keyguardViewMediator,
+            AssistManager assistManager,
+            DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            VisualStabilityManager visualStabilityManager,
+            PulseExpansionHandler pulseExpansionHandler,
+            StatusBarWindowController statusBarWindowController,
+            NotificationWakeUpCoordinator notificationWakeUpCoordinator) {
+        super();
+        mDozeLog = dozeLog;
+        mPowerManager = powerManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mStatusBarStateController = statusBarStateController;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mHeadsUpManagerPhone = headsUpManagerPhone;
+        mBatteryController = batteryController;
+        mScrimController = scrimController;
+        mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mKeyguardViewMediator = keyguardViewMediator;
+        mAssistManager = assistManager;
+        mDozeScrimController = dozeScrimController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mVisualStabilityManager = visualStabilityManager;
+        mPulseExpansionHandler = pulseExpansionHandler;
+        mStatusBarWindowController = statusBarWindowController;
+        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+    }
+
+    // TODO: we should try to not pass status bar in here if we can avoid it.
+
+    /**
+     * Initialize instance with objects only available later during execution.
+     */
+    public void initialize(StatusBar statusBar,
+            NotificationIconAreaController notificationIconAreaController,
+            StatusBarWindowViewController statusBarWindowViewController,
+            StatusBarWindowView statusBarWindow,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+        mStatusBar = statusBar;
+        mNotificationIconAreaController = notificationIconAreaController;
+        mStatusBarWindowViewController = statusBarWindowViewController;
+        mStatusBarWindow = statusBarWindow;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mNotificationPanel = notificationPanel;
+        mAmbientIndicationContainer = ambientIndicationContainer;
+        mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+    }
+
+    @Override
+    public String toString() {
+        return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
+    }
+
+    void firePowerSaveChanged(boolean active) {
+        for (Callback callback : mCallbacks) {
+            callback.onPowerSaveChanged(active);
+        }
+    }
+
+    void fireNotificationPulse(NotificationEntry entry) {
+        Runnable pulseSuppressedListener = () -> {
+            entry.setPulseSuppressed(true);
+            mNotificationIconAreaController.updateAodNotificationIcons();
+        };
+        for (Callback callback : mCallbacks) {
+            callback.onNotificationAlerted(pulseSuppressedListener);
+        }
+    }
+
+    boolean getDozingRequested() {
+        return mDozingRequested;
+    }
+
+    boolean isPulsing() {
+        return mPulsing;
+    }
+
+
+    @Override
+    public void addCallback(@NonNull Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    @Override
+    public void startDozing() {
+        if (!mDozingRequested) {
+            mDozingRequested = true;
+            mDozeLog.traceDozing(mDozing);
+            updateDozing();
+            mStatusBar.updateIsKeyguard();
+        }
+    }
+
+    void updateDozing() {
+        // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
+        boolean
+                dozing =
+                mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+                        || mBiometricUnlockController.getMode()
+                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+        // When in wake-and-unlock we may not have received a change to StatusBarState
+        // but we still should not be dozing, manually set to false.
+        if (mBiometricUnlockController.getMode()
+                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
+            dozing = false;
+        }
+
+
+        mStatusBarStateController.setIsDozing(dozing);
+    }
+
+    @Override
+    public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
+            mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                                 "com.android.systemui:LONG_PRESS");
+            mAssistManager.startAssist(new Bundle());
+            return;
+        }
+
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+            mScrimController.setWakeLockScreenSensorActive(true);
+        }
+
+        if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
+            mStatusBarWindowViewController.suppressWakeUpGesture(true);
+        }
+
+        boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+                        && mWakeLockScreenPerformsAuth;
+        // Set the state to pulsing, so ScrimController will know what to do once we ask it to
+        // execute the transition. The pulse callback will then be invoked when the scrims
+        // are black, indicating that StatusBar is ready to present the rest of the UI.
+        mPulsing = true;
+        mDozeScrimController.pulse(new PulseCallback() {
+            @Override
+            public void onPulseStarted() {
+                callback.onPulseStarted();
+                mStatusBar.updateNotificationPanelTouchState();
+                setPulsing(true);
+            }
+
+            @Override
+            public void onPulseFinished() {
+                mPulsing = false;
+                callback.onPulseFinished();
+                mStatusBar.updateNotificationPanelTouchState();
+                mScrimController.setWakeLockScreenSensorActive(false);
+                if (mStatusBarWindow != null) {
+                    mStatusBarWindowViewController.suppressWakeUpGesture(false);
+                }
+                setPulsing(false);
+            }
+
+            private void setPulsing(boolean pulsing) {
+                mStatusBarStateController.setPulsing(pulsing);
+                mStatusBarKeyguardViewManager.setPulsing(pulsing);
+                mKeyguardViewMediator.setPulsing(pulsing);
+                mNotificationPanel.setPulsing(pulsing);
+                mVisualStabilityManager.setPulsing(pulsing);
+                mStatusBarWindowViewController.setPulsing(pulsing);
+                mIgnoreTouchWhilePulsing = false;
+                if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
+                    mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
+                }
+                mStatusBar.updateScrimController();
+                mPulseExpansionHandler.setPulsing(pulsing);
+                mNotificationWakeUpCoordinator.setPulsing(pulsing);
+            }
+        }, reason);
+        // DozeScrimController is in pulse state, now let's ask ScrimController to start
+        // pulsing and draw the black frame, if necessary.
+        mStatusBar.updateScrimController();
+    }
+
+    @Override
+    public void stopDozing() {
+        if (mDozingRequested) {
+            mDozingRequested = false;
+            mDozeLog.traceDozing(mDozing);
+            updateDozing();
+        }
+    }
+
+    @Override
+    public void onIgnoreTouchWhilePulsing(boolean ignore) {
+        if (ignore != mIgnoreTouchWhilePulsing) {
+            mDozeLog.tracePulseTouchDisabledByProx(ignore);
+        }
+        mIgnoreTouchWhilePulsing = ignore;
+        if (mDozing && ignore) {
+            mStatusBarWindowViewController.cancelCurrentTouch();
+        }
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        mNotificationPanel.dozeTimeTick();
+        if (mAmbientIndicationContainer instanceof DozeReceiver) {
+            ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+        }
+    }
+
+    @Override
+    public boolean isPowerSaveActive() {
+        return mBatteryController.isAodPowerSave();
+    }
+
+    @Override
+    public boolean isPulsingBlocked() {
+        return mBiometricUnlockController.getMode()
+                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+    }
+
+    @Override
+    public boolean isProvisioned() {
+        return mDeviceProvisionedController.isDeviceProvisioned()
+                && mDeviceProvisionedController.isCurrentUserSetup();
+    }
+
+    @Override
+    public boolean isBlockingDoze() {
+        if (mBiometricUnlockController.hasPendingAuthentication()) {
+            Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void extendPulse(int reason) {
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+            mScrimController.setWakeLockScreenSensorActive(true);
+        }
+        if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
+            mHeadsUpManagerPhone.extendHeadsUp();
+        } else {
+            mDozeScrimController.extendPulse();
+        }
+    }
+
+    @Override
+    public void stopPulsing() {
+        if (mDozeScrimController.isPulsing()) {
+            mDozeScrimController.pulseOutNow();
+        }
+    }
+
+    @Override
+    public void setAnimateWakeup(boolean animateWakeup) {
+        if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+                || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
+            // Too late to change the wakeup animation.
+            return;
+        }
+        mAnimateWakeup = animateWakeup;
+    }
+
+    @Override
+    public void setAnimateScreenOff(boolean animateScreenOff) {
+        mAnimateScreenOff = animateScreenOff;
+    }
+
+    @Override
+    public void onSlpiTap(float screenX, float screenY) {
+        if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+                && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
+            int[] locationOnScreen = new int[2];
+            mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
+            float viewX = screenX - locationOnScreen[0];
+            float viewY = screenY - locationOnScreen[1];
+            if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
+                    && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
+
+                // Dispatch a tap
+                long now = SystemClock.elapsedRealtime();
+                MotionEvent ev = MotionEvent.obtain(
+                        now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0);
+                mAmbientIndicationContainer.dispatchTouchEvent(ev);
+                ev.recycle();
+                ev = MotionEvent.obtain(
+                        now, now, MotionEvent.ACTION_UP, screenX, screenY, 0);
+                mAmbientIndicationContainer.dispatchTouchEvent(ev);
+                ev.recycle();
+            }
+        }
+    }
+
+    @Override
+    public void setDozeScreenBrightness(int value) {
+        mStatusBarWindowController.setDozeScreenBrightness(value);
+    }
+
+    @Override
+    public void setAodDimmingScrim(float scrimOpacity) {
+        mScrimController.setAodFrontScrimAlpha(scrimOpacity);
+    }
+
+
+
+    @Override
+    public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+        if (mPendingScreenOffCallback != null) {
+            Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+        }
+        mPendingScreenOffCallback = onDisplayOffCallback;
+        mStatusBar.updateScrimController();
+    }
+
+    @Override
+    public void cancelGentleSleep() {
+        mPendingScreenOffCallback = null;
+        if (mScrimController.getState() == ScrimState.OFF) {
+            mStatusBar.updateScrimController();
+        }
+    }
+
+    /**
+     * When the dozing host is waiting for scrims to fade out to change the display state.
+     */
+    boolean hasPendingScreenOffCallback() {
+        return mPendingScreenOffCallback != null;
+    }
+
+    /**
+     * Executes an nullifies the pending display state callback.
+     *
+     * @see #hasPendingScreenOffCallback()
+     * @see #prepareForGentleSleep(Runnable)
+     */
+    void executePendingScreenOffCallback() {
+        if (mPendingScreenOffCallback == null) {
+            return;
+        }
+        mPendingScreenOffCallback.run();
+        mPendingScreenOffCallback = null;
+    }
+
+    boolean shouldAnimateWakeup() {
+        return mAnimateWakeup;
+    }
+
+    boolean shouldAnimateScreenOff() {
+        return mAnimateScreenOff;
+    }
+
+    public void setDozing(boolean dozing) {
+        mDozing = dozing;
+    }
+
+    boolean getIgnoreTouchWhilePulsing() {
+        return mIgnoreTouchWhilePulsing;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 179375e..4e91e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -240,7 +240,7 @@
      * @return Alpha from 0 to 1.
      */
     private float getClockAlpha(int y) {
-        float alphaKeyguard = Math.max(0, y / Math.max(1f, getExpandedPreferredClockY()));
+        float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f)));
         alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
         return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 6064fbe..35039a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,11 +60,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
  */
+@Singleton
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2e0fbfa..afc147a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -50,7 +50,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -157,10 +156,8 @@
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -347,7 +344,7 @@
     /**
      * The {@link StatusBarState} of the status bar.
      */
-    protected int mState;
+    protected int mState; // TODO: remove this. Just use StatusBarStateController
     protected boolean mBouncerShowing;
 
     private PhoneStatusBarPolicy mIconPolicy;
@@ -373,7 +370,7 @@
     protected StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
-    DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+    DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
 
@@ -493,7 +490,6 @@
     private final UiOffloadThread mUiOffloadThread;
 
     protected boolean mDozing;
-    private boolean mDozingRequested;
 
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -612,7 +608,6 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     protected StatusBarNotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
-    private boolean mPulsing;
     private final BubbleController mBubbleController;
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
@@ -692,7 +687,10 @@
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
-            Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            DozeServiceHost dozeServiceHost,
+            PowerManager powerManager,
+            DozeScrimController dozeScrimController) {
         super(context);
         mFeatureFlags = featureFlags;
         mLightBarController = lightBarController;
@@ -749,9 +747,12 @@
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
         mNotifLog = notifLog;
+        mDozeServiceHost = dozeServiceHost;
+        mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
         mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+        mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
 
         mBubbleExpandListener =
@@ -804,7 +805,6 @@
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -904,6 +904,9 @@
         startKeyguard();
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
+        mDozeServiceHost.initialize(this, mNotificationIconAreaController,
+                mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+                mNotificationPanel, mAmbientIndicationContainer);
         putComponent(DozeHost.class, mDozeServiceHost);
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
@@ -1068,7 +1071,6 @@
 
         mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
                 mHeadsUpManager, mNotificationIconAreaController, mScrimController);
-        mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
 
         BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1526,7 +1528,6 @@
                         .start();
             }
         }
-        mMediaManager.findAndUpdateMediaNotifications();
     }
 
     private void updateReportRejectedTouchVisibility() {
@@ -1729,7 +1730,7 @@
         if (isDozing() && isHeadsUp) {
             entry.setPulseSuppressed(false);
             mDozeServiceHost.fireNotificationPulse(entry);
-            if (mPulsing) {
+            if (mDozeServiceHost.isPulsing()) {
                 mDozeScrimController.cancelPendingPulseTimeout();
             }
         }
@@ -1761,7 +1762,7 @@
     }
 
     public boolean isPulsing() {
-        return mPulsing;
+        return mDozeServiceHost.isPulsing();
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
@@ -2824,7 +2825,7 @@
         if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mStatusBarStateController.leaveOpenOnKeyguardHide()
-                && isPulsing()) {
+                && mDozeServiceHost.isPulsing()) {
             // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
             // TODO: Factor this transition out of BiometricUnlockController.
             mBiometricUnlockController.startWakeAndUnlock(
@@ -3155,7 +3156,7 @@
         return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
     }
 
-    private boolean updateIsKeyguard() {
+    boolean updateIsKeyguard() {
         boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
@@ -3163,8 +3164,8 @@
         // there's no surface we can show to the user. Note that the device goes fully interactive
         // late in the transition, so we also allow the device to start dozing once the screen has
         // turned off fully.
-        boolean keyguardForDozing = mDozingRequested &&
-                (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
+        boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
+                && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
         boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
                 || keyguardForDozing) && !wakeAndUnlocking;
         if (keyguardForDozing) {
@@ -3580,7 +3581,7 @@
     public void onStateChanged(int newState) {
         mState = newState;
         updateReportRejectedTouchVisibility();
-        updateDozing();
+        mDozeServiceHost.updateDozing();
         updateTheme();
         mNavigationBarController.touchAutoDim(mDisplayId);
         Trace.beginSection("StatusBar#updateKeyguardState");
@@ -3618,9 +3619,11 @@
     public void onDozingChanged(boolean isDozing) {
         Trace.beginSection("StatusBar#updateDozing");
         mDozing = isDozing;
+        mDozeServiceHost.setDozing(mDozing);
 
         // Collapse the notification panel if open
-        boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
+        boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
+                && mDozeParameters.shouldControlScreenOff();
         mNotificationPanel.resetViews(dozingAnimated);
 
         updateQsExpansionEnabled();
@@ -3628,26 +3631,12 @@
 
         mEntryManager.updateNotifications("onDozingChanged");
         updateDozingState();
+        mDozeServiceHost.updateDozing();
         updateScrimController();
         updateReportRejectedTouchVisibility();
         Trace.endSection();
     }
 
-    private void updateDozing() {
-        // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
-        boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
-                || mBiometricUnlockController.getMode()
-                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
-        // When in wake-and-unlock we may not have received a change to mState
-        // but we still should not be dozing, manually set to false.
-        if (mBiometricUnlockController.getMode() ==
-                BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
-            dozing = false;
-        }
-
-        mStatusBarStateController.setIsDozing(dozing);
-    }
-
     private void updateKeyguardState() {
         mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mStatusBarKeyguardViewManager.isOccluded());
@@ -3872,10 +3861,11 @@
      * collapse the panel after we expanded it, and thus we would end up with a blank
      * Keyguard.
      */
-    private void updateNotificationPanelTouchState() {
+    void updateNotificationPanelTouchState() {
         boolean goingToSleepWithoutAnimation = isGoingToSleep()
                 && !mDozeParameters.shouldControlScreenOff();
-        boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
+        boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
+                || goingToSleepWithoutAnimation;
         mNotificationPanel.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
@@ -4025,7 +4015,7 @@
     }
 
     public void notifyBiometricAuthModeChanged() {
-        updateDozing();
+        mDozeServiceHost.updateDozing();
         updateScrimController();
         mStatusBarWindowViewController.onBiometricAuthModeChanged(
                 mBiometricUnlockController.isWakeAndUnlock(),
@@ -4061,7 +4051,7 @@
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         } else if (mBrightnessMirrorVisible) {
             mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
-        } else if (isPulsing()) {
+        } else if (mDozeServiceHost.isPulsing()) {
             mScrimController.transitionTo(ScrimState.PULSING,
                     mDozeScrimController.getScrimCallback());
         } else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
@@ -4091,295 +4081,8 @@
         return mStatusBarKeyguardViewManager.isShowing();
     }
 
-    @VisibleForTesting
-    final class DozeServiceHost implements DozeHost {
-        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-        private boolean mAnimateWakeup;
-        private boolean mAnimateScreenOff;
-        private boolean mIgnoreTouchWhilePulsing;
-        private Runnable mPendingScreenOffCallback;
-        @VisibleForTesting
-        boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
-                "persist.sysui.wake_performs_auth", true);
-
-        @Override
-        public String toString() {
-            return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
-        }
-
-        public void firePowerSaveChanged(boolean active) {
-            for (Callback callback : mCallbacks) {
-                callback.onPowerSaveChanged(active);
-            }
-        }
-
-        public void fireNotificationPulse(NotificationEntry entry) {
-            Runnable pulseSupressedListener = () -> {
-                entry.setPulseSuppressed(true);
-                mNotificationIconAreaController.updateAodNotificationIcons();
-            };
-            for (Callback callback : mCallbacks) {
-                callback.onNotificationAlerted(pulseSupressedListener);
-            }
-        }
-
-        @Override
-        public void addCallback(@NonNull Callback callback) {
-            mCallbacks.add(callback);
-        }
-
-        @Override
-        public void removeCallback(@NonNull Callback callback) {
-            mCallbacks.remove(callback);
-        }
-
-        @Override
-        public void startDozing() {
-            if (!mDozingRequested) {
-                mDozingRequested = true;
-                mDozeLog.traceDozing(mDozing);
-                updateDozing();
-                updateIsKeyguard();
-            }
-        }
-
-        @Override
-        public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
-                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
-                        "com.android.systemui:LONG_PRESS");
-                startAssist(new Bundle());
-                return;
-            }
-
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
-                mScrimController.setWakeLockScreenSensorActive(true);
-            }
-
-            if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
-                mStatusBarWindowViewController.suppressWakeUpGesture(true);
-            }
-
-            boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
-                            && mWakeLockScreenPerformsAuth;
-            // Set the state to pulsing, so ScrimController will know what to do once we ask it to
-            // execute the transition. The pulse callback will then be invoked when the scrims
-            // are black, indicating that StatusBar is ready to present the rest of the UI.
-            mPulsing = true;
-            mDozeScrimController.pulse(new PulseCallback() {
-                @Override
-                public void onPulseStarted() {
-                    callback.onPulseStarted();
-                    updateNotificationPanelTouchState();
-                    setPulsing(true);
-                }
-
-                @Override
-                public void onPulseFinished() {
-                    mPulsing = false;
-                    callback.onPulseFinished();
-                    updateNotificationPanelTouchState();
-                    mScrimController.setWakeLockScreenSensorActive(false);
-                    if (mStatusBarWindow != null) {
-                        mStatusBarWindowViewController.suppressWakeUpGesture(false);
-                    }
-                    setPulsing(false);
-                }
-
-                private void setPulsing(boolean pulsing) {
-                    mStatusBarStateController.setPulsing(pulsing);
-                    mStatusBarKeyguardViewManager.setPulsing(pulsing);
-                    mKeyguardViewMediator.setPulsing(pulsing);
-                    mNotificationPanel.setPulsing(pulsing);
-                    mVisualStabilityManager.setPulsing(pulsing);
-                    mStatusBarWindowViewController.setPulsing(pulsing);
-                    mIgnoreTouchWhilePulsing = false;
-                    if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
-                        mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
-                    }
-                    updateScrimController();
-                    mPulseExpansionHandler.setPulsing(pulsing);
-                    mWakeUpCoordinator.setPulsing(pulsing);
-                }
-            }, reason);
-            // DozeScrimController is in pulse state, now let's ask ScrimController to start
-            // pulsing and draw the black frame, if necessary.
-            updateScrimController();
-        }
-
-        @Override
-        public void stopDozing() {
-            if (mDozingRequested) {
-                mDozingRequested = false;
-                mDozeLog.traceDozing(mDozing);
-                updateDozing();
-            }
-        }
-
-        @Override
-        public void onIgnoreTouchWhilePulsing(boolean ignore) {
-            if (ignore != mIgnoreTouchWhilePulsing) {
-                mDozeLog.tracePulseTouchDisabledByProx(ignore);
-            }
-            mIgnoreTouchWhilePulsing = ignore;
-            if (isDozing() && ignore) {
-                mStatusBarWindowViewController.cancelCurrentTouch();
-            }
-        }
-
-        @Override
-        public void dozeTimeTick() {
-            mNotificationPanel.dozeTimeTick();
-            if (mAmbientIndicationContainer instanceof DozeReceiver) {
-                ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
-            }
-        }
-
-        @Override
-        public boolean isPowerSaveActive() {
-            return mBatteryController.isAodPowerSave();
-        }
-
-        @Override
-        public boolean isPulsingBlocked() {
-            return mBiometricUnlockController.getMode()
-                    == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
-        }
-
-        @Override
-        public boolean isProvisioned() {
-            return mDeviceProvisionedController.isDeviceProvisioned()
-                    && mDeviceProvisionedController.isCurrentUserSetup();
-        }
-
-        @Override
-        public boolean isBlockingDoze() {
-            if (mBiometricUnlockController.hasPendingAuthentication()) {
-                Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void extendPulse(int reason) {
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
-                mScrimController.setWakeLockScreenSensorActive(true);
-            }
-            if (mDozeScrimController.isPulsing() && mHeadsUpManager.hasNotifications()) {
-                mHeadsUpManager.extendHeadsUp();
-            } else {
-                mDozeScrimController.extendPulse();
-            }
-        }
-
-        @Override
-        public void stopPulsing() {
-            if (mDozeScrimController.isPulsing()) {
-                mDozeScrimController.pulseOutNow();
-            }
-        }
-
-        @Override
-        public void setAnimateWakeup(boolean animateWakeup) {
-            if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
-                    || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
-                // Too late to change the wakeup animation.
-                return;
-            }
-            mAnimateWakeup = animateWakeup;
-        }
-
-        @Override
-        public void setAnimateScreenOff(boolean animateScreenOff) {
-            mAnimateScreenOff = animateScreenOff;
-        }
-
-        @Override
-        public void onSlpiTap(float screenX, float screenY) {
-            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
-                && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
-                mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
-                float viewX = screenX - mTmpInt2[0];
-                float viewY = screenY - mTmpInt2[1];
-                if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
-                        && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
-                    dispatchTap(mAmbientIndicationContainer, viewX, viewY);
-                }
-            }
-        }
-
-        @Override
-        public void setDozeScreenBrightness(int value) {
-            mStatusBarWindowController.setDozeScreenBrightness(value);
-        }
-
-        @Override
-        public void setAodDimmingScrim(float scrimOpacity) {
-            mScrimController.setAodFrontScrimAlpha(scrimOpacity);
-        }
-
-        @Override
-        public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
-            if (mPendingScreenOffCallback != null) {
-                Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
-            }
-            mPendingScreenOffCallback = onDisplayOffCallback;
-            updateScrimController();
-        }
-
-        @Override
-        public void cancelGentleSleep() {
-            mPendingScreenOffCallback = null;
-            if (mScrimController.getState() == ScrimState.OFF) {
-                updateScrimController();
-            }
-        }
-
-        /**
-         * When the dozing host is waiting for scrims to fade out to change the display state.
-         */
-        boolean hasPendingScreenOffCallback() {
-            return mPendingScreenOffCallback != null;
-        }
-
-        /**
-         * Executes an nullifies the pending display state callback.
-         *
-         * @see #hasPendingScreenOffCallback()
-         * @see #prepareForGentleSleep(Runnable)
-         */
-        void executePendingScreenOffCallback() {
-            if (mPendingScreenOffCallback == null) {
-                return;
-            }
-            mPendingScreenOffCallback.run();
-            mPendingScreenOffCallback = null;
-        }
-
-        private void dispatchTap(View view, float x, float y) {
-            long now = SystemClock.elapsedRealtime();
-            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
-            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
-        }
-
-        private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
-            MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
-            view.dispatchTouchEvent(ev);
-            ev.recycle();
-        }
-
-        private boolean shouldAnimateWakeup() {
-            return mAnimateWakeup;
-        }
-
-        public boolean shouldAnimateScreenOff() {
-            return mAnimateScreenOff;
-        }
-    }
-
     public boolean shouldIgnoreTouch() {
-        return isDozing() && mDozeServiceHost.mIgnoreTouchWhilePulsing;
+        return isDozing() && mDozeServiceHost.getIgnoreTouchWhilePulsing();
     }
 
     // Begin Extra BaseStatusBar methods.
@@ -4406,7 +4109,7 @@
     private boolean mVisibleToUser;
 
     protected DevicePolicyManager mDevicePolicyManager;
-    protected PowerManager mPowerManager;
+    private final PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     protected KeyguardManager mKeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 6edd75b..0c68383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -123,6 +123,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
         context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null,
                 bgHandler);
 
@@ -298,14 +299,11 @@
         } else {
             mVpnUserId = mCurrentUserId;
         }
-        refreshCACerts();
         fireCallbacks();
     }
 
-    private void refreshCACerts() {
-        new CACertLoader().execute(mCurrentUserId);
-        int workProfileId = getWorkProfileUserId(mCurrentUserId);
-        if (workProfileId != UserHandle.USER_NULL) new CACertLoader().execute(workProfileId);
+    private void refreshCACerts(int userId) {
+        new CACertLoader().execute(userId);
     }
 
     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
@@ -401,7 +399,10 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
-                refreshCACerts();
+                refreshCACerts(getSendingUserId());
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                if (userId != UserHandle.USER_NULL) refreshCACerts(userId);
             }
         }
     };
@@ -416,9 +417,6 @@
                 return new Pair<Integer, Boolean>(userId[0], hasCACerts);
             } catch (RemoteException | InterruptedException | AssertionError e) {
                 Log.i(TAG, "failed to get CA certs", e);
-                mBgHandler.postDelayed(
-                        () -> new CACertLoader().execute(userId[0]),
-                        CA_CERT_LOADING_RETRY_TIME_IN_MS);
                 return new Pair<Integer, Boolean>(userId[0], null);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index f2d2fae..2c99668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -196,7 +198,10 @@
                 }
                 ArrayList<UserRecord> records = new ArrayList<>(infos.size());
                 int currentId = ActivityManager.getCurrentUser();
-                boolean canSwitchUsers = mUserManager.canSwitchUsers();
+                // Check user switchability of the foreground user since SystemUI is running in
+                // User 0
+                boolean canSwitchUsers = mUserManager.getUserSwitchability(
+                        UserHandle.of(ActivityManager.getCurrentUser())) == SWITCHABILITY_STATUS_OK;
                 UserInfo currentUserInfo = null;
                 UserRecord guestRecord = null;
 
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index fa3ff64..0b27327 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -35,6 +35,7 @@
 import android.widget.CheckBox;
 
 import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.systemui.R;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
new file mode 100644
index 0000000..b05172c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DozeServiceHostTest extends SysuiTestCase {
+
+    private DozeServiceHost mDozeServiceHost;
+
+    @Mock private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock private ScrimController mScrimController;
+    @Mock private DozeScrimController mDozeScrimController;
+    @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private KeyguardViewMediator mKeyguardViewMediator;
+    @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+    @Mock private BatteryController mBatteryController;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private AssistManager mAssistManager;
+    @Mock private DozeLog mDozeLog;
+    @Mock private PulseExpansionHandler mPulseExpansionHandler;
+    @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+    @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private PowerManager mPowerManager;
+    @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
+    @Mock private StatusBar mStatusBar;
+    @Mock private NotificationIconAreaController mNotificationIconAreaController;
+    @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+    @Mock private StatusBarWindowView mStatusBarWindow;
+    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private NotificationPanelView mNotificationPanel;
+    @Mock private View mAmbientIndicationContainer;
+    @Mock private BiometricUnlockController mBiometricUnlockController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+        mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
+                mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
+                mBatteryController, mScrimController, mBiometricUnlockControllerLazy,
+                mKeyguardViewMediator, mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor,
+                mVisualStabilityManager, mPulseExpansionHandler, mStatusBarWindowController,
+                mNotificationWakeUpCoordinator);
+
+        mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
+                mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+                mNotificationPanel, mAmbientIndicationContainer);
+    }
+
+    @Test
+    public void testStartStopDozing() {
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+        when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+        assertFalse(mDozeServiceHost.getDozingRequested());
+
+        mDozeServiceHost.startDozing();
+        verify(mStatusBarStateController).setIsDozing(eq(true));
+        verify(mStatusBar).updateIsKeyguard();
+
+        mDozeServiceHost.stopDozing();
+        verify(mStatusBarStateController).setIsDozing(eq(false));
+    }
+
+
+    @Test
+    public void testPulseWhileDozing_updatesScrimController() {
+        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+        mStatusBar.showKeyguardImpl();
+
+        // Keep track of callback to be able to stop the pulse
+//        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+//        doAnswer(invocation -> {
+//            pulseCallback[0] = invocation.getArgument(0);
+//            return null;
+//        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        // Starting a pulse should change the scrim controller to the pulsing state
+        mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+            @Override
+            public void onPulseStarted() {
+            }
+
+            @Override
+            public void onPulseFinished() {
+            }
+        }, DozeEvent.PULSE_REASON_NOTIFICATION);
+
+        ArgumentCaptor<DozeHost.PulseCallback> pulseCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(DozeHost.PulseCallback.class);
+
+        verify(mDozeScrimController).pulse(
+                pulseCallbackArgumentCaptor.capture(), eq(DozeEvent.PULSE_REASON_NOTIFICATION));
+        verify(mStatusBar).updateScrimController();
+        reset(mStatusBar);
+
+        pulseCallbackArgumentCaptor.getValue().onPulseFinished();
+        assertFalse(mDozeScrimController.isPulsing());
+        verify(mStatusBar).updateScrimController();
+    }
+
+
+    @Test
+    public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
+        // Keep track of callback to be able to stop the pulse
+        final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+        doAnswer(invocation -> {
+            pulseCallback[0] = invocation.getArgument(0);
+            return null;
+        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        // Starting a pulse while docking should suppress wakeup gesture
+        mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
+                DozeEvent.PULSE_REASON_DOCKING);
+        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
+
+        // Ending a pulse should restore wakeup gesture
+        pulseCallback[0].onPulseFinished();
+        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
+    }
+
+    @Test
+    public void testPulseWhileDozing_notifyAuthInterrupt() {
+        HashSet<Integer> reasonsWantingAuth = new HashSet<>(
+                Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+        HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
+                Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
+                        DozeEvent.PULSE_REASON_NOTIFICATION,
+                        DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
+                        DozeEvent.REASON_SENSOR_PICKUP,
+                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+                        DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
+                        DozeEvent.PULSE_REASON_DOCKING,
+                        DozeEvent.REASON_SENSOR_WAKE_UP,
+                        DozeEvent.REASON_SENSOR_TAP));
+        HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
+                Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
+                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+                        DozeEvent.REASON_SENSOR_TAP));
+
+        doAnswer(invocation -> {
+            DozeHost.PulseCallback callback = invocation.getArgument(0);
+            callback.onPulseStarted();
+            return null;
+        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
+        for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
+            reset(mKeyguardUpdateMonitor);
+            mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
+            if (reasonsWantingAuth.contains(i)) {
+                verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
+            } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
+                verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
+            } else {
+                throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
+                        + " passive auth. Please consider how this pulse reason should behave.");
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f5e92e4..66c01ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -85,8 +85,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
-import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -145,9 +143,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
 
 import dagger.Lazy;
 
@@ -231,6 +226,7 @@
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
+    @Mock private DozeServiceHost mDozeServiceHost;
     @Mock private LinearLayout mLockIconContainer;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
 
@@ -365,7 +361,10 @@
                 mDozeParameters,
                 mScrimController,
                 mLockscreenWallpaperLazy,
-                mBiometricUnlockControllerLazy);
+                mBiometricUnlockControllerLazy,
+                mDozeServiceHost,
+                mPowerManager,
+                mDozeScrimController);
 
         when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
@@ -388,7 +387,6 @@
         mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
-        mStatusBar.mPowerManager = mPowerManager;
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController;
@@ -757,83 +755,18 @@
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         mStatusBar.showKeyguardImpl();
 
-        // Keep track of callback to be able to stop the pulse
-        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-        doAnswer(invocation -> {
-            pulseCallback[0] = invocation.getArgument(0);
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
         // Starting a pulse should change the scrim controller to the pulsing state
-        mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
-                DozeEvent.PULSE_REASON_NOTIFICATION);
+        when(mDozeServiceHost.isPulsing()).thenReturn(true);
+        mStatusBar.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any());
 
         // Ending a pulse should take it back to keyguard state
-        pulseCallback[0].onPulseFinished();
+        when(mDozeServiceHost.isPulsing()).thenReturn(false);
+        mStatusBar.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
     }
 
     @Test
-    public void testPulseWhileDozing_notifyAuthInterrupt() {
-        HashSet<Integer> reasonsWantingAuth = new HashSet<>(
-                Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
-        HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
-                Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
-                        DozeEvent.PULSE_REASON_NOTIFICATION,
-                        DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
-                        DozeEvent.REASON_SENSOR_PICKUP,
-                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
-                        DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
-                        DozeEvent.PULSE_REASON_DOCKING,
-                        DozeEvent.REASON_SENSOR_WAKE_UP,
-                        DozeEvent.REASON_SENSOR_TAP));
-        HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
-                Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
-                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
-                        DozeEvent.REASON_SENSOR_TAP));
-
-        doAnswer(invocation -> {
-            DozeHost.PulseCallback callback = invocation.getArgument(0);
-            callback.onPulseStarted();
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
-        mStatusBar.mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
-        for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
-            reset(mKeyguardUpdateMonitor);
-            mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
-            if (reasonsWantingAuth.contains(i)) {
-                verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
-            } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
-                verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
-            } else {
-                throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
-                        + " passive auth. Please consider how this pulse reason should behave.");
-            }
-        }
-    }
-
-    @Test
-    public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
-        // Keep track of callback to be able to stop the pulse
-        final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-        doAnswer(invocation -> {
-            pulseCallback[0] = invocation.getArgument(0);
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
-        // Starting a pulse while docking should suppress wakeup gesture
-        mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
-                DozeEvent.PULSE_REASON_DOCKING);
-        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
-
-        // Ending a pulse should restore wakeup gesture
-        pulseCallback[0].onPulseFinished();
-        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
-    }
-
-    @Test
     public void testSetState_changesIsFullScreenUserSwitcherState() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -859,27 +792,17 @@
     }
 
     @Test
-    public void testStartStopDozing() {
-        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
-        when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-
-        mStatusBar.mDozeServiceHost.startDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(true));
-
-        mStatusBar.mDozeServiceHost.stopDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(false));
-    }
-
-    @Test
     public void testOnStartedWakingUp_isNotDozing() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-        mStatusBar.mDozeServiceHost.startDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(true));
+        when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+        mStatusBar.updateIsKeyguard();
+        // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
+        verify(mNotificationPanelView, times(2)).expand(eq(false));
         clearInvocations(mNotificationPanelView);
 
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
-        verify(mStatusBarStateController).setIsDozing(eq(false));
+        verify(mDozeServiceHost).stopDozing();
         verify(mNotificationPanelView).expand(eq(false));
     }
 
@@ -887,7 +810,8 @@
     public void testOnStartedWakingUp_doesNotDismissBouncer_whenPulsing() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-        mStatusBar.mDozeServiceHost.startDozing();
+        when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+        mStatusBar.updateIsKeyguard();
         clearInvocations(mNotificationPanelView);
 
         mStatusBar.setBouncerShowing(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 854cc2f..97542a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -93,9 +93,9 @@
         when(mKeyChainService.queryLocalInterface("android.security.IKeyChainService"))
                 .thenReturn(mKeyChainService);
 
-        // Wait for callbacks from 1) the CACertLoader and 2) the onUserSwitched() function in the
+        // Wait for callbacks from the onUserSwitched() function in the
         // constructor of mSecurityController
-        mStateChangedLatch = new CountDownLatch(2);
+        mStateChangedLatch = new CountDownLatch(1);
         // TODO: Migrate this test to TestableLooper and use a handler attached
         // to that.
         mSecurityController = new SecurityControllerImpl(mContext,
@@ -169,7 +169,6 @@
         assertTrue(mSecurityController.hasCACertInCurrentUser());
 
         // Exception
-
         mStateChangedLatch = new CountDownLatch(1);
 
         when(mKeyChainService.getUserCaAliases())
@@ -181,9 +180,12 @@
 
         assertFalse(mStateChangedLatch.await(1, TimeUnit.SECONDS));
         assertTrue(mSecurityController.hasCACertInCurrentUser());
-        // The retry takes 30s
-        //assertTrue(mStateChangedLatch.await(31, TimeUnit.SECONDS));
-        //assertFalse(mSecurityController.hasCACertInCurrentUser());
+
+        mSecurityController.new CACertLoader()
+                           .execute(0);
+
+        assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
+        assertFalse(mSecurityController.hasCACertInCurrentUser());
     }
 
     @Test
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index ca69c18..2bfe287 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -21,6 +21,7 @@
         "src/**/*.java",
         ":framework-tethering-shared-srcs",
         ":services-tethering-shared-srcs",
+        ":servicescore-tethering-src",
     ],
     static_libs: [
         "androidx.annotation_annotation",
@@ -67,9 +68,17 @@
 
 // This group will be removed when tethering migration is done.
 filegroup {
-    name: "tethering-services-srcs",
+    name: "tethering-servicescore-srcs",
     srcs: [
+        "src/com/android/server/connectivity/tethering/EntitlementManager.java",
         "src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
+    ],
+}
+
+// This group will be removed when tethering migration is done.
+filegroup {
+    name: "tethering-servicesnet-srcs",
+    srcs: [
         "src/android/net/dhcp/DhcpServerCallbacks.java",
         "src/android/net/dhcp/DhcpServingParamsParcelExt.java",
         "src/android/net/ip/IpServer.java",
diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
similarity index 99%
rename from services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index f952bce..6b0f1de 100644
--- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -87,7 +87,6 @@
     private static final int EVENT_MAYBE_RUN_PROVISIONING = 3;
     private static final int EVENT_GET_ENTITLEMENT_VALUE = 4;
 
-
     // The ArraySet contains enabled downstream types, ex:
     // {@link ConnectivityManager.TETHERING_WIFI}
     // {@link ConnectivityManager.TETHERING_USB}
@@ -112,7 +111,6 @@
 
     public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log,
             int permissionChangeMessageCode, MockableSystemProperties systemProperties) {
-
         mContext = ctx;
         mLog = log.forSubComponent(TAG);
         mCurrentTethers = new ArraySet<Integer>();
@@ -138,7 +136,7 @@
         /**
          * Ui entitlement check fails in |downstream|.
          *
-         * @param downstream  tethering type from ConnectivityManager.TETHERING_{@code *}.
+         * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}.
          */
         void onUiEntitlementFailed(int downstream);
     }
@@ -662,7 +660,6 @@
 
     private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver,
             boolean showEntitlementUi) {
-
         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
         if (!isTetherProvisioningRequired(config)) {
             receiver.send(TETHER_ERROR_NO_ERROR, null);
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index da62107..5564bd6 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -17,7 +17,10 @@
 android_test {
     name: "TetheringTests",
     certificate: "platform",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        ":servicescore-tethering-src",
+        "src/**/*.java",
+    ],
     test_suites: ["device-tests"],
     static_libs: [
         "androidx.test.rules",
@@ -42,6 +45,7 @@
 filegroup {
     name: "tethering-tests-src",
     srcs: [
+        "src/com/android/server/connectivity/tethering/EntitlementManagerTest.java",
         "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
         "src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
         "src/android/net/ip/IpServerTest.java",
diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
similarity index 100%
rename from tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
rename to packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
deleted file mode 100644
index bd682de..0000000
--- a/proto/src/wifi.proto
+++ /dev/null
@@ -1,2707 +0,0 @@
-/*
- * Copyright (C) 2016 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 clearcut.connectivity;
-
-option java_package = "com.android.server.wifi";
-option java_outer_classname = "WifiMetricsProto";
-
-// The information about the Wifi events.
-message WifiLog {
-
-  // Session information that gets logged for every Wifi connection.
-  repeated ConnectionEvent connection_event = 1;
-
-  // Number of saved networks in the user profile.
-  optional int32 num_saved_networks = 2;
-
-  // Number of open networks in the saved networks.
-  optional int32 num_open_networks = 3;
-
-  // Number of legacy personal networks.
-  optional int32 num_legacy_personal_networks = 4;
-
-  // Number of legacy enterprise networks.
-  optional int32 num_legacy_enterprise_networks = 5;
-
-  // Does the user have location setting enabled.
-  optional bool is_location_enabled = 6;
-
-  // Does the user have scanning enabled.
-  optional bool is_scanning_always_enabled = 7;
-
-  // Number of times user toggled wifi using the settings menu.
-  optional int32 num_wifi_toggled_via_settings = 8;
-
-  // Number of times user toggled wifi using the airplane menu.
-  optional int32 num_wifi_toggled_via_airplane = 9;
-
-  // Number of networks added by the user.
-  optional int32 num_networks_added_by_user = 10;
-
-  // Number of networks added by applications.
-  optional int32 num_networks_added_by_apps = 11;
-
-  // Number scans that returned empty results.
-  optional int32 num_empty_scan_results = 12;
-
-  // Number scans that returned at least one result.
-  optional int32 num_non_empty_scan_results = 13;
-
-  // Number of single scans requests.
-  optional int32 num_oneshot_scans = 14;
-
-  // Number of repeated background scans that were scheduled to the chip.
-  optional int32 num_background_scans = 15;
-
-  // Error codes that a scan can result in.
-  enum ScanReturnCode {
-
-    // Return Code is unknown.
-    SCAN_UNKNOWN = 0;
-
-    // Scan was successful.
-    SCAN_SUCCESS = 1;
-
-    // Scan was successfully started, but was interrupted.
-    SCAN_FAILURE_INTERRUPTED = 2;
-
-    //  Scan failed to start because of invalid configuration
-    //  (bad channel, etc).
-    SCAN_FAILURE_INVALID_CONFIGURATION = 3;
-
-    // Could not start a scan because wifi is disabled.
-    FAILURE_WIFI_DISABLED = 4;
-
-  }
-
-  // Mapping of error codes to the number of times that scans resulted
-  // in that error.
-  repeated ScanReturnEntry scan_return_entries = 16;
-
-  message ScanReturnEntry {
-
-    // Return code of the scan.
-    optional ScanReturnCode scan_return_code = 1;
-
-    // Number of entries that were found in the scan.
-    optional int32 scan_results_count = 2;
-  }
-
-  // State of the Wifi.
-  enum WifiState {
-
-    // State is unknown.
-    WIFI_UNKNOWN = 0;
-
-    // Wifi is disabled.
-    WIFI_DISABLED = 1;
-
-    // Wifi is enabled.
-    WIFI_DISCONNECTED = 2;
-
-    // Wifi is enabled and associated with an AP.
-    WIFI_ASSOCIATED = 3;
-  }
-
-  // Mapping of system state to the number of times that scans were requested in
-  // that state
-  repeated WifiSystemStateEntry wifi_system_state_entries = 17;
-
-  message WifiSystemStateEntry {
-
-    // Current WiFi state.
-    optional WifiState wifi_state = 1;
-
-    // Count of scans in state.
-    optional int32 wifi_state_count = 2;
-
-    // Is screen on.
-    optional bool is_screen_on = 3;
-  }
-
-  // Mapping of Error/Success codes to the number of background scans that resulted in it
-  repeated ScanReturnEntry background_scan_return_entries = 18;
-
-  // Mapping of system state to the number of times that Background scans were requested in that
-  // state
-  repeated WifiSystemStateEntry background_scan_request_state = 19;
-
-  // Total number of times the Watchdog of Last Resort triggered, resetting the wifi stack
-  optional int32 num_last_resort_watchdog_triggers = 20;
-
-  // Total number of networks over bad association threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_association_networks_total = 21;
-
-  // Total number of networks over bad authentication threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_authentication_networks_total = 22;
-
-  // Total number of networks over bad dhcp threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_dhcp_networks_total = 23;
-
-  // Total number of networks over bad other threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_other_networks_total = 24;
-
-  // Total count of networks seen when watchdog triggered
-  optional int32 num_last_resort_watchdog_available_networks_total = 25;
-
-  // Total count of triggers with atleast one bad association network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_association = 26;
-
-  // Total count of triggers with atleast one bad authentication network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_authentication = 27;
-
-  // Total count of triggers with atleast one bad dhcp network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_dhcp = 28;
-
-  // Total count of triggers with atleast one bad other network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_other = 29;
-
-  // Count of times connectivity watchdog confirmed pno is working
-  optional int32 num_connectivity_watchdog_pno_good = 30;
-
-  // Count of times connectivity watchdog found pno not working
-  optional int32 num_connectivity_watchdog_pno_bad = 31;
-
-  // Count of times connectivity watchdog confirmed background scan is working
-  optional int32 num_connectivity_watchdog_background_good = 32;
-
-  // Count of times connectivity watchdog found background scan not working
-  optional int32 num_connectivity_watchdog_background_bad = 33;
-
-  // The time duration represented by this wifi log, from start to end of capture
-  optional int32 record_duration_sec = 34;
-
-  // Counts the occurrences of each individual RSSI poll level
-  repeated RssiPollCount rssi_poll_rssi_count = 35;
-
-  // Total number of times WiFi connected immediately after a Last Resort Watchdog trigger,
-  // without new networks becoming available.
-  optional int32 num_last_resort_watchdog_successes = 36;
-
-  // Total number of saved hidden networks
-  optional int32 num_hidden_networks = 37;
-
-  // Total number of saved passpoint / hotspot 2.0 networks
-  optional int32 num_passpoint_networks = 38;
-
-  // Total number of scan results
-  optional int32 num_total_scan_results = 39;
-
-  // Total number of scan results for open networks
-  optional int32 num_open_network_scan_results = 40;
-
-  // Total number of scan results for legacy personal networks
-  optional int32 num_legacy_personal_network_scan_results = 41;
-
-  // Total number of scan results for legacy enterprise networks
-  optional int32 num_legacy_enterprise_network_scan_results = 42;
-
-  // Total number of scan results for hidden networks
-  optional int32 num_hidden_network_scan_results = 43;
-
-  // Total number of scan results for hotspot 2.0 r1 networks
-  optional int32 num_hotspot2_r1_network_scan_results = 44;
-
-  // Total number of scan results for hotspot 2.0 r2 networks
-  optional int32 num_hotspot2_r2_network_scan_results = 45;
-
-  // Total number of scans handled by framework (oneshot or otherwise)
-  optional int32 num_scans = 46;
-
-  // Counts the occurrences of each alert reason.
-  repeated AlertReasonCount alert_reason_count = 47;
-
-  // Counts the occurrences of each Wifi score
-  repeated WifiScoreCount wifi_score_count = 48;
-
-  // Histogram of Soft AP Durations
-  repeated SoftApDurationBucket soft_ap_duration = 49;
-
-  // Histogram of Soft AP ReturnCode
-  repeated SoftApReturnCodeCount soft_ap_return_code = 50;
-
-  // Histogram of the delta between scan result RSSI and RSSI polls
-  repeated RssiPollCount rssi_poll_delta_count = 51;
-
-  // List of events
-  repeated StaEvent sta_event_list = 52;
-
-  // Total number of times WiFi HAL crashed.
-  optional int32 num_hal_crashes = 53;
-
-  // Total number of times WiFicond crashed.
-  optional int32 num_wificond_crashes = 54;
-
-  // Indicates the number of times an error was encountered in
-  // Wifi HAL on |WifiNative.setupInterfaceForClientMode|.
-  optional int32 num_setup_client_interface_failure_due_to_hal = 55;
-
-  // Indicates the number of times an error was encountered in
-  // Wificond on |WifiNative.setupInterfaceForClientMode|.
-  optional int32 num_setup_client_interface_failure_due_to_wificond = 56;
-
-  // Wi-Fi Aware metrics
-  optional WifiAwareLog wifi_aware_log = 57;
-
-  // Number of saved Passpoint providers in user profile.
-  optional int32 num_passpoint_providers = 58;
-
-  // Count of times Passpoint provider being installed.
-  optional int32 num_passpoint_provider_installation = 59;
-
-  // Count of times Passpoint provivider is installed successfully.
-  optional int32 num_passpoint_provider_install_success = 60;
-
-  // Count of times Passpoint provider is being uninstalled.
-  optional int32 num_passpoint_provider_uninstallation = 61;
-
-  // Count of times Passpoint provider is uninstalled successfully.
-  optional int32 num_passpoint_provider_uninstall_success = 62;
-
-  // Count of saved Passpoint providers device has ever connected to.
-  optional int32 num_passpoint_providers_successfully_connected = 63;
-
-  // Histogram counting instances of scans with N many ScanResults with unique ssids
-  repeated NumConnectableNetworksBucket total_ssids_in_scan_histogram = 64;
-
-  // Histogram counting instances of scans with N many ScanResults/bssids
-  repeated NumConnectableNetworksBucket total_bssids_in_scan_histogram = 65;
-
-  // Histogram counting instances of scans with N many unique open ssids
-  repeated NumConnectableNetworksBucket available_open_ssids_in_scan_histogram = 66;
-
-  // Histogram counting instances of scans with N many bssids for open networks
-  repeated NumConnectableNetworksBucket available_open_bssids_in_scan_histogram = 67;
-
-  // Histogram counting instances of scans with N many unique ssids for saved networks
-  repeated NumConnectableNetworksBucket available_saved_ssids_in_scan_histogram = 68;
-
-  // Histogram counting instances of scans with N many bssids for saved networks
-  repeated NumConnectableNetworksBucket available_saved_bssids_in_scan_histogram = 69;
-
-  // Histogram counting instances of scans with N many unique SSIDs for open or saved networks
-  repeated NumConnectableNetworksBucket available_open_or_saved_ssids_in_scan_histogram = 70;
-
-  // Histogram counting instances of scans with N many BSSIDs for open or saved networks
-  repeated NumConnectableNetworksBucket available_open_or_saved_bssids_in_scan_histogram = 71;
-
-  // Histogram counting instances of scans with N many ScanResults matching unique saved passpoint providers
-  repeated NumConnectableNetworksBucket available_saved_passpoint_provider_profiles_in_scan_histogram = 72;
-
-  // Histogram counting instances of scans with N many ScanResults BSSIDs matching a saved passpoint provider
-  repeated NumConnectableNetworksBucket available_saved_passpoint_provider_bssids_in_scan_histogram = 73;
-
-  // Counts the number of AllSingleScanLister.onResult calls with a full band scan result
-  optional int32 full_band_all_single_scan_listener_results = 74;
-
-  // Counts the number of AllSingleScanLister.onResult calls with a partial (channels) scan result
-  optional int32 partial_all_single_scan_listener_results = 75;
-
-  // Pno scan metrics
-  optional PnoScanMetrics pno_scan_metrics = 76;
-
-  // Histogram of "Connect to Network" notifications.
-  // The notification Action should be unset.
-  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_count = 77;
-
-  // Histogram of "Connect to Network" notification user actions.
-  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_action_count = 78;
-
-  // The number of SSIDs blacklisted from recommendation by the open network
-  // notification recommender
-  optional int32 open_network_recommender_blacklist_size = 79;
-
-  // Is the available network notification feature turned on
-  optional bool is_wifi_networks_available_notification_on = 80;
-
-  // Count of recommendation updates made by the open network notification
-  // recommender
-  optional int32 num_open_network_recommendation_updates = 81;
-
-  // Count of connection attempts that were initiated unsuccessfully
-  optional int32 num_open_network_connect_message_failed_to_send = 82;
-
-  // Histogram counting instances of scans with N many HotSpot 2.0 R1 APs
-  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_in_scan_histogram = 83;
-
-  // Histogram counting instances of scans with N many HotSpot 2.0 R2 APs
-  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_in_scan_histogram = 84;
-
-  // Histogram counting instances of scans with N many unique HotSpot 2.0 R1 ESS.
-  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
-  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
-  // order).
-  repeated NumConnectableNetworksBucket observed_hotspot_r1_ess_in_scan_histogram = 85;
-
-  // Histogram counting instances of scans with N many unique HotSpot 2.0 R2 ESS.
-  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
-  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
-  // order).
-  repeated NumConnectableNetworksBucket observed_hotspot_r2_ess_in_scan_histogram = 86;
-
-  // Histogram counting number of HotSpot 2.0 R1 APs per observed ESS in a scan
-  // (one value added per unique ESS - potentially multiple counts per single
-  // scan!)
-  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_per_ess_in_scan_histogram = 87;
-
-  // Histogram counting number of HotSpot 2.0 R2 APs per observed ESS in a scan
-  // (one value added per unique ESS - potentially multiple counts per single
-  // scan!)
-  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_per_ess_in_scan_histogram = 88;
-
-  // SoftAP event list tracking sessions and client counts in tethered mode
-  repeated SoftApConnectedClientsEvent soft_ap_connected_clients_events_tethered = 89;
-
-  // SoftAP event list tracking sessions and client counts in local only mode
-  repeated SoftApConnectedClientsEvent soft_ap_connected_clients_events_local_only = 90;
-
-  // Wps connection metrics
-  optional WpsMetrics wps_metrics = 91;
-
-  // Wifi power statistics
-  optional WifiPowerStats wifi_power_stats = 92;
-
-  // Number of connectivity single scan requests.
-  optional int32 num_connectivity_oneshot_scans = 93;
-
-  // WifiWake statistics
-  optional WifiWakeStats wifi_wake_stats = 94;
-
-  // Histogram counting instances of scans with N many 802.11mc (RTT) supporting APs
-  repeated NumConnectableNetworksBucket observed_80211mc_supporting_aps_in_scan_histogram = 95;
-
-  // Total number of times supplicant crashed.
-  optional int32 num_supplicant_crashes = 96;
-
-  // Total number of times hostapd crashed.
-  optional int32 num_hostapd_crashes = 97;
-
-  // Indicates the number of times an error was encountered in
-  // supplicant on |WifiNative.setupInterfaceForClientMode|.
-  optional int32 num_setup_client_interface_failure_due_to_supplicant = 98;
-
-  // Indicates the number of times an error was encountered in
-  // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|.
-  optional int32 num_setup_soft_ap_interface_failure_due_to_hal = 99;
-
-  // Indicates the number of times an error was encountered in
-  // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|.
-  optional int32 num_setup_soft_ap_interface_failure_due_to_wificond = 100;
-
-  // Indicates the number of times an error was encountered in
-  // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|.
-  optional int32 num_setup_soft_ap_interface_failure_due_to_hostapd = 101;
-
-  // Indicates the number of times we got an interface down in client mode.
-  optional int32 num_client_interface_down = 102;
-
-  // Indicates the number of times we got an interface down in softap mode.
-  optional int32 num_soft_ap_interface_down = 103;
-
-  // Indicates the number of scan requests from external apps.
-  optional int32 num_external_app_oneshot_scan_requests = 104;
-
-  // Indicates the number of times a scan request from an external foreground app was throttled.
-  optional int32 num_external_foreground_app_oneshot_scan_requests_throttled = 105;
-
-  // Indicates the number of times a scan request from an external background app was throttled.
-  optional int32 num_external_background_app_oneshot_scan_requests_throttled = 106;
-
-  // WifiLastResortWatchdog time milliseconds delta between trigger and first connection success
-  optional int64 watchdog_trigger_to_connection_success_duration_ms = 107 [default = -1];
-
-  // The number of times wifi experienced failures after watchdog has already been triggered and is
-  // waiting for a connection success
-  optional int64 watchdog_total_connection_failure_count_after_trigger = 108;
-
-  // Number of times DFS channel scans are requested in single scan requests.
-  optional int32 num_oneshot_has_dfs_channel_scans = 109;
-
-  // Wi-Fi RTT metrics
-  optional WifiRttLog wifi_rtt_log = 110;
-
-  // Flag which indicates if Connected MAC Randomization is enabled
-  optional bool is_mac_randomization_on = 111 [default = false];
-
-  // Number of radio mode changes to MCC (Multi channel concurrency).
-  optional int32 num_radio_mode_change_to_mcc = 112;
-
-  // Number of radio mode changes to SCC (Single channel concurrency).
-  optional int32 num_radio_mode_change_to_scc = 113;
-
-  // Number of radio mode changes to SBS (Single band simultaneous).
-  optional int32 num_radio_mode_change_to_sbs = 114;
-
-  // Number of radio mode changes to DBS (Dual band simultaneous).
-  optional int32 num_radio_mode_change_to_dbs = 115;
-
-  // Number of times the firmware picked a SoftAp channel not satisfying user band preference.
-  optional int32 num_soft_ap_user_band_preference_unsatisfied = 116;
-
-  // Identifier for experimental scoring parameter settings.
-  optional string score_experiment_id = 117;
-
-  // Data on wifi radio usage
-  optional WifiRadioUsage wifi_radio_usage = 118;
-
-  // Stores settings values used for metrics testing.
-  optional ExperimentValues experiment_values = 119;
-
-  // List of WifiIsUnusableEvents which get logged when we notice that WiFi is unusable.
-  // Collected only when WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED Settings is enabled.
-  repeated WifiIsUnusableEvent wifi_is_unusable_event_list = 120;
-
-  // Counts the occurrences of each link speed (Mbps) level
-  // with rssi (dBm) and rssi^2 sums (dBm^2)
-  repeated LinkSpeedCount link_speed_counts = 121;
-
-  // Number of times the SarManager failed to register SAR sensor listener
-  optional int32 num_sar_sensor_registration_failures = 122;
-
-  // Hardware revision (EVT, DVT, PVT etc.)
-  optional string hardware_revision = 124;
-
-  // Total wifi link layer usage data over the logging duration in ms.
-  optional WifiLinkLayerUsageStats wifi_link_layer_usage_stats = 125;
-
-  // Multiple lists of timestamped link layer stats with labels to represent whether wifi is usable
-  repeated WifiUsabilityStats wifi_usability_stats_list = 126;
-
-  // Counts the occurrences of each Wifi usability score provided by external app
-  repeated WifiUsabilityScoreCount wifi_usability_score_count = 127;
-
-  // List of PNO scan stats, one element for each mobility state
-  repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
-
-  // Wifi p2p statistics
-  optional WifiP2pStats wifi_p2p_stats = 129;
-
-  // Easy Connect (DPP) metrics
-  optional WifiDppLog wifi_dpp_log = 130;
-
-  // Number of Enhanced Open (OWE) networks in the saved networks.
-  optional int32 num_enhanced_open_networks = 131;
-
-  // Number of WPA3-Personal networks.
-  optional int32 num_wpa3_personal_networks = 132;
-
-  // Number of WPA3-Enterprise networks.
-  optional int32 num_wpa3_enterprise_networks = 133;
-
-  // Total number of scan results for Enhanced open networks
-  optional int32 num_enhanced_open_network_scan_results = 134;
-
-  // Total number of scan results for WPA3-Personal networks
-  optional int32 num_wpa3_personal_network_scan_results = 135;
-
-  // Total number of scan results for WPA3-Enterprise networks
-  optional int32 num_wpa3_enterprise_network_scan_results = 136;
-
-  // WifiConfigStore read/write metrics.
-  optional WifiConfigStoreIO wifi_config_store_io = 137;
-
-  // Total number of saved networks with mac randomization enabled.
-  optional int32 num_saved_networks_with_mac_randomization = 138;
-
-  // Link Probe metrics
-  optional LinkProbeStats link_probe_stats = 139;
-
-  // List of NetworkSelectionExperimentDecisions stats for each experiment
-  repeated NetworkSelectionExperimentDecisions network_selection_experiment_decisions_list = 140;
-
-  // Network Request API surface metrics.
-  optional WifiNetworkRequestApiLog wifi_network_request_api_log = 141;
-
-  // Network Suggestion API surface metrics.
-  optional WifiNetworkSuggestionApiLog wifi_network_suggestion_api_log = 142;
-
-  // WifiLock statistics
-  optional WifiLockStats wifi_lock_stats = 143;
-
-  // Stats on number of times Wi-Fi is turned on/off though the WifiManager#setWifiEnabled API
-  optional WifiToggleStats wifi_toggle_stats = 144;
-
-  // Number of times WifiManager#addOrUpdateNetwork is called.
-  optional int32 num_add_or_update_network_calls = 145;
-
-  // Number of times WifiManager#enableNetwork is called.
-  optional int32 num_enable_network_calls = 146;
-
-  // Passpoint provison metrics
-  optional PasspointProvisionStats passpoint_provision_stats = 147;
-
-  // Histogram of the EAP method type of all installed Passpoint profiles for R1
-  repeated PasspointProfileTypeCount installed_passpoint_profile_type_for_r1 = 123;
-
-  // Histogram of the EAP method type of all installed Passpoint profiles for R2
-  repeated PasspointProfileTypeCount installed_passpoint_profile_type_for_r2 = 148;
-
-  // Histogram of Tx link speed at 2G
-  repeated Int32Count tx_link_speed_count_2g = 149;
-
-  // Histogram of Tx link speed at 5G low band
-  repeated Int32Count tx_link_speed_count_5g_low = 150;
-
-  // Histogram of Tx link speed at 5G middle band
-  repeated Int32Count tx_link_speed_count_5g_mid = 151;
-
-  // Histogram of Tx link speed at 5G high band
-  repeated Int32Count tx_link_speed_count_5g_high = 152;
-
-  // Histogram of Rx link speed at 2G
-  repeated Int32Count rx_link_speed_count_2g = 153;
-
-  // Histogram of Rx link speed at 5G low band
-  repeated Int32Count rx_link_speed_count_5g_low = 154;
-
-  // Histogram of Rx link speed at 5G middle band
-  repeated Int32Count rx_link_speed_count_5g_mid = 155;
-
-  // Histogram of Rx link speed at 5G high band
-  repeated Int32Count rx_link_speed_count_5g_high = 156;
-}
-
-// Information that gets logged for every WiFi connection.
-message RouterFingerPrint {
-
-  enum RoamType {
-
-    // Type is unknown.
-    ROAM_TYPE_UNKNOWN = 0;
-
-    // No roaming - usually happens on a single band (2.4 GHz) router.
-    ROAM_TYPE_NONE = 1;
-
-    // Enterprise router.
-    ROAM_TYPE_ENTERPRISE = 2;
-
-    // DBDC => Dual Band Dual Concurrent essentially a router that
-    // supports both 2.4 GHz and 5 GHz bands.
-    ROAM_TYPE_DBDC = 3;
-  }
-
-  enum Auth {
-
-    // Auth is unknown.
-    AUTH_UNKNOWN = 0;
-
-    // No authentication.
-    AUTH_OPEN = 1;
-
-    // If the router uses a personal authentication.
-    AUTH_PERSONAL = 2;
-
-    // If the router is setup for enterprise authentication.
-    AUTH_ENTERPRISE = 3;
-  }
-
-  enum RouterTechnology {
-
-    // Router is unknown.
-    ROUTER_TECH_UNKNOWN = 0;
-
-    // Router Channel A.
-    ROUTER_TECH_A = 1;
-
-    // Router Channel B.
-    ROUTER_TECH_B = 2;
-
-    // Router Channel G.
-    ROUTER_TECH_G = 3;
-
-    // Router Channel N.
-    ROUTER_TECH_N = 4;
-
-    // Router Channel AC.
-    ROUTER_TECH_AC = 5;
-
-    // When the channel is not one of the above.
-    ROUTER_TECH_OTHER = 6;
-  }
-
-  optional RoamType roam_type = 1;
-
-  // Channel on which the connection takes place.
-  optional int32 channel_info = 2;
-
-  // DTIM setting of the router.
-  optional int32 dtim = 3;
-
-  // Authentication scheme of the router.
-  optional Auth authentication = 4;
-
-  // If the router is hidden.
-  optional bool hidden = 5;
-
-  // Channel information.
-  optional RouterTechnology router_technology = 6;
-
-  // whether ipv6 is supported.
-  optional bool supports_ipv6 = 7;
-
-  // If the router is a passpoint / hotspot 2.0 network
-  optional bool passpoint = 8;
-}
-
-message ConnectionEvent {
-
-  // Roam Type.
-  enum RoamType {
-
-    // Type is unknown.
-    ROAM_UNKNOWN = 0;
-
-    // No roaming.
-    ROAM_NONE = 1;
-
-    // DBDC roaming.
-    ROAM_DBDC = 2;
-
-    // Enterprise roaming.
-    ROAM_ENTERPRISE = 3;
-
-    // User selected roaming.
-    ROAM_USER_SELECTED = 4;
-
-    // Unrelated.
-    ROAM_UNRELATED = 5;
-  }
-
-  // Connectivity Level Failure.
-  enum ConnectivityLevelFailure {
-
-    // Failure is unknown.
-    HLF_UNKNOWN = 0;
-
-    // No failure.
-    HLF_NONE = 1;
-
-    // DHCP failure.
-    HLF_DHCP = 2;
-
-    // No internet connection.
-    HLF_NO_INTERNET = 3;
-
-    // No internet connection.
-    HLF_UNWANTED = 4;
-  }
-
-  // Level 2 failure reason.
-  enum Level2FailureReason {
-
-    // Unknown default
-    FAILURE_REASON_UNKNOWN = 0;
-
-    // The reason code if there is no error during authentication. It could
-    // also imply that there no authentication in progress.
-    AUTH_FAILURE_NONE = 1;
-
-    // The reason code if there was a timeout authenticating.
-    AUTH_FAILURE_TIMEOUT = 2;
-
-    // The reason code if there was a wrong password while authenticating.
-    AUTH_FAILURE_WRONG_PSWD = 3;
-
-    // The reason code if there was EAP failure while authenticating.
-    AUTH_FAILURE_EAP_FAILURE = 4;
-
-    // The reason code if the AP can no longer accept new clients.
-    ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA = 5;
-  }
-
-  // Entity that recommended connecting to this network.
-  enum ConnectionNominator {
-    // Unknown nominator
-    NOMINATOR_UNKNOWN = 0;
-
-    // User selected network manually
-    NOMINATOR_MANUAL = 1;
-
-    // Saved network
-    NOMINATOR_SAVED = 2;
-
-    // Suggestion API
-    NOMINATOR_SUGGESTION = 3;
-
-    // Passpoint
-    NOMINATOR_PASSPOINT = 4;
-
-    // Carrier suggestion
-    NOMINATOR_CARRIER = 5;
-
-    // External scorer
-    NOMINATOR_EXTERNAL_SCORED = 6;
-
-    // Network Specifier
-    NOMINATOR_SPECIFIER = 7;
-
-    // User connected choice override
-    NOMINATOR_SAVED_USER_CONNECT_CHOICE = 8;
-
-    // Open Network Available Pop-up
-    NOMINATOR_OPEN_NETWORK_AVAILABLE = 9;
-  }
-
-  // Start time of the connection.
-  optional int64 start_time_millis = 1;// [(datapol.semantic_type) = ST_TIMESTAMP];
-
-  // Duration to connect.
-  optional int32 duration_taken_to_connect_millis = 2;
-
-  // Router information.
-  optional RouterFingerPrint router_fingerprint = 3;
-
-  // RSSI at the start of the connection.
-  optional int32 signal_strength = 4;
-
-  // Roam Type.
-  optional RoamType roam_type = 5;
-
-  // Result of the connection.
-  optional int32 connection_result = 6;
-
-  // Reasons for level 2 failure (needs to be coordinated with wpa-supplicant).
-  optional int32 level_2_failure_code = 7;
-
-  // Failures that happen at the connectivity layer.
-  optional ConnectivityLevelFailure connectivity_level_failure_code = 8;
-
-  // Has bug report been taken.
-  optional bool automatic_bug_report_taken = 9;
-
-  // Connection is using locally generated random MAC address.
-  optional bool use_randomized_mac = 10 [default = false];
-
-  // Who chose to connect.
-  optional ConnectionNominator connection_nominator = 11;
-
-  // The currently running network selector when this connection event occurred.
-  optional int32 network_selector_experiment_id = 12;
-
-  // Breakdown of level_2_failure_code with more detailed reason.
-  optional Level2FailureReason level_2_failure_reason = 13
-          [default = FAILURE_REASON_UNKNOWN];
-}
-
-// Number of occurrences of a specific RSSI poll rssi value
-message RssiPollCount {
-  // RSSI
-  optional int32 rssi = 1;
-
-  // Number of RSSI polls with 'rssi'
-  optional int32 count = 2;
-
-  // Beacon frequency of the channel in MHz
-  optional int32 frequency = 3;
-}
-
-// Number of occurrences of a specific alert reason value
-message AlertReasonCount {
-  // Alert reason
-  optional int32 reason = 1;
-
-  // Number of alerts with |reason|.
-  optional int32 count = 2;
-}
-
-// Counts the number of instances of a specific Wifi Score calculated by WifiScoreReport
-message WifiScoreCount {
-  // Wifi Score
-  optional int32 score = 1;
-
-  // Number of Wifi score reports with this score
-  optional int32 count = 2;
-}
-
-// Counts the number of instances of a specific Wifi Usability Score
-message WifiUsabilityScoreCount {
-  // Wifi Usability Score
-  optional int32 score = 1;
-
-  // Number of Wifi score reports with this score
-  optional int32 count = 2;
-}
-
-// Number of occurrences of a specific link speed (Mbps)
-// and sum of rssi (dBm) and rssi^2 (dBm^2)
-message LinkSpeedCount {
-  // Link speed (Mbps)
-  optional int32 link_speed_mbps = 1;
-
-  // Number of RSSI polls with link_speed
-  optional int32 count = 2;
-
-  // Sum of absolute values of rssi values (dBm)
-  optional int32 rssi_sum_dbm = 3;
-
-  // Sum of squares of rssi values (dBm^2)
-  optional int64 rssi_sum_of_squares_dbm_sq = 4;
-}
-
-
-// Number of occurrences of Soft AP session durations
-message SoftApDurationBucket {
-  // Bucket covers duration : [duration_sec, duration_sec + bucket_size_sec)
-  // The (inclusive) lower bound of Soft AP session duration represented by this bucket
-  optional int32 duration_sec = 1;
-
-  // The size of this bucket
-  optional int32 bucket_size_sec = 2;
-
-  // Number of soft AP session durations that fit into this bucket
-  optional int32 count = 3;
-}
-
-// Number of occurrences of a soft AP session return code
-message SoftApReturnCodeCount {
-
-  enum SoftApStartResult {
-
-    // SoftApManager return code unknown
-    SOFT_AP_RETURN_CODE_UNKNOWN = 0;
-
-    // SoftAp started successfully
-    SOFT_AP_STARTED_SUCCESSFULLY = 1;
-
-    // Catch all for failures with no specific failure reason
-    SOFT_AP_FAILED_GENERAL_ERROR = 2;
-
-    // SoftAp failed to start due to NO_CHANNEL error
-    SOFT_AP_FAILED_NO_CHANNEL = 3;
-  }
-
-  // Historical, no longer used for writing as of 01/2017.
-  optional int32 return_code = 1 [deprecated = true];
-
-  // Occurrences of this soft AP return code
-  optional int32 count = 2;
-
-  // Result of attempt to start SoftAp
-  optional SoftApStartResult start_result = 3;
-}
-
-message StaEvent {
-  message ConfigInfo {
-    // The set of key management protocols supported by this configuration.
-    optional uint32 allowed_key_management = 1 [default = 0];
-
-    // The set of security protocols supported by this configuration.
-    optional uint32 allowed_protocols = 2 [default = 0];
-
-    // The set of authentication protocols supported by this configuration.
-    optional uint32 allowed_auth_algorithms = 3 [default = 0];
-
-    // The set of pairwise ciphers for WPA supported by this configuration.
-    optional uint32 allowed_pairwise_ciphers = 4 [default = 0];
-
-    // The set of group ciphers supported by this configuration.
-    optional uint32 allowed_group_ciphers = 5;
-
-    // Is this a 'hidden network'
-    optional bool hidden_ssid = 6;
-
-    // Is this a Hotspot 2.0 / passpoint network
-    optional bool is_passpoint = 7;
-
-    // Is this an 'ephemeral' network (Not in saved network list, recommended externally)
-    optional bool is_ephemeral = 8;
-
-    // Has a successful connection ever been established using this WifiConfiguration
-    optional bool has_ever_connected = 9;
-
-    // RSSI of the scan result candidate associated with this WifiConfiguration
-    optional int32 scan_rssi = 10 [default = -127];
-
-    // Frequency of the scan result candidate associated with this WifiConfiguration
-    optional int32 scan_freq = 11 [default = -1];
-  }
-
-  enum EventType {
-    // Default/Invalid event
-    TYPE_UNKNOWN = 0;
-
-    // Supplicant Association Rejection event. Code contains the 802.11
-    TYPE_ASSOCIATION_REJECTION_EVENT = 1;
-
-    // Supplicant L2 event,
-    TYPE_AUTHENTICATION_FAILURE_EVENT = 2;
-
-    // Supplicant L2 event
-    TYPE_NETWORK_CONNECTION_EVENT = 3;
-
-    // Supplicant L2 event
-    TYPE_NETWORK_DISCONNECTION_EVENT = 4;
-
-    // Supplicant L2 event
-    TYPE_SUPPLICANT_STATE_CHANGE_EVENT = 5;
-
-    // Supplicant L2 event
-    TYPE_CMD_ASSOCIATED_BSSID = 6;
-
-    // IP Manager successfully completed IP Provisioning
-    TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL = 7;
-
-    // IP Manager failed to complete IP Provisioning
-    TYPE_CMD_IP_CONFIGURATION_LOST = 8;
-
-    // IP Manager lost reachability to network neighbors
-    TYPE_CMD_IP_REACHABILITY_LOST = 9;
-
-    // Indicator that Supplicant is targeting a BSSID for roam/connection
-    TYPE_CMD_TARGET_BSSID = 10;
-
-    // Wifi framework is initiating a connection attempt
-    TYPE_CMD_START_CONNECT = 11;
-
-    // Wifi framework is initiating a roaming connection attempt
-    TYPE_CMD_START_ROAM = 12;
-
-    // SystemAPI connect() command, Settings App
-    TYPE_CONNECT_NETWORK = 13;
-
-    // Network Agent has validated the internet connection (Captive Portal Check success, or user
-    // validation)
-    TYPE_NETWORK_AGENT_VALID_NETWORK = 14;
-
-    // Framework initiated disconnect. Sometimes generated to give an extra reason for a disconnect
-    // Should typically be followed by a NETWORK_DISCONNECTION_EVENT with a local_gen = true
-    TYPE_FRAMEWORK_DISCONNECT = 15;
-
-    // The NetworkAgent score for wifi has changed in a way that may impact
-    // connectivity
-    TYPE_SCORE_BREACH = 16;
-
-    // Framework changed Sta interface MAC address
-    TYPE_MAC_CHANGE = 17;
-
-    // Wifi is turned on
-    TYPE_WIFI_ENABLED = 18;
-
-    // Wifi is turned off
-    TYPE_WIFI_DISABLED = 19;
-
-    // The NetworkAgent Wifi usability score has changed in a way that may
-    // impact connectivity
-    TYPE_WIFI_USABILITY_SCORE_BREACH = 20;
-
-    // Link probe was performed
-    TYPE_LINK_PROBE = 21;
-  }
-
-  enum FrameworkDisconnectReason {
-    // default/none/unknown value
-    DISCONNECT_UNKNOWN = 0;
-
-    // API DISCONNECT
-    DISCONNECT_API = 1;
-
-    // Some framework internal reason (generic)
-    DISCONNECT_GENERIC = 2;
-
-    // Network Agent network validation failed, user signaled network unwanted
-    DISCONNECT_UNWANTED = 3;
-
-    // Roaming timed out
-    DISCONNECT_ROAM_WATCHDOG_TIMER = 4;
-
-    // P2P service requested wifi disconnect
-    DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST = 5;
-
-    // SIM was removed while using a SIM config
-    DISCONNECT_RESET_SIM_NETWORKS = 6;
-  }
-
-  // Authentication Failure reasons as reported through the API.
-  enum AuthFailureReason {
-    // Unknown default
-    AUTH_FAILURE_UNKNOWN = 0;
-
-    // The reason code if there is no error during authentication. It could also imply that there no
-    // authentication in progress,
-    AUTH_FAILURE_NONE = 1;
-
-    // The reason code if there was a timeout authenticating.
-    AUTH_FAILURE_TIMEOUT = 2;
-
-    // The reason code if there was a wrong password while authenticating.
-    AUTH_FAILURE_WRONG_PSWD = 3;
-
-    // The reason code if there was EAP failure while authenticating.
-    AUTH_FAILURE_EAP_FAILURE = 4;
-  }
-
-  // What event was this
-  optional EventType type = 1;
-
-  // 80211 death reason code, relevant to NETWORK_DISCONNECTION_EVENTs
-  optional int32 reason = 2 [default = -1];
-
-  // 80211 Association Status code, relevant to ASSOCIATION_REJECTION_EVENTs
-  optional int32 status = 3 [default = -1];
-
-  // Designates whether a NETWORK_DISCONNECT_EVENT was by the STA or AP
-  optional bool local_gen = 4 [default = false];
-
-  // Network information from the WifiConfiguration of a framework initiated connection attempt
-  optional ConfigInfo config_info = 5;
-
-  // RSSI from the last rssi poll (Only valid for active connections)
-  optional int32 last_rssi = 6 [default = -127];
-
-  // Link speed from the last rssi poll (Only valid for active connections)
-  optional int32 last_link_speed = 7 [default = -1];
-
-  // Frequency from the last rssi poll (Only valid for active connections)
-  optional int32 last_freq = 8 [default = -1];
-
-  // Enum used to define bit positions in the supplicant_state_change_bitmask
-  // See {@code frameworks/base/wifi/java/android/net/wifi/SupplicantState.java} for documentation
-  enum SupplicantState {
-    STATE_DISCONNECTED = 0;
-
-    STATE_INTERFACE_DISABLED = 1;
-
-    STATE_INACTIVE = 2;
-
-    STATE_SCANNING = 3;
-
-    STATE_AUTHENTICATING = 4;
-
-    STATE_ASSOCIATING = 5;
-
-    STATE_ASSOCIATED = 6;
-
-    STATE_FOUR_WAY_HANDSHAKE = 7;
-
-    STATE_GROUP_HANDSHAKE = 8;
-
-    STATE_COMPLETED = 9;
-
-    STATE_DORMANT = 10;
-
-    STATE_UNINITIALIZED = 11;
-
-    STATE_INVALID = 12;
-  }
-
-  // Bit mask of all supplicant state changes that occurred since the last event
-  optional uint32 supplicant_state_changes_bitmask = 9 [default = 0];
-
-  // The number of milliseconds that have elapsed since the device booted
-  optional int64 start_time_millis = 10 [default = 0];
-
-  optional FrameworkDisconnectReason framework_disconnect_reason = 11 [default = DISCONNECT_UNKNOWN];
-
-  // Flag which indicates if an association rejection event occurred due to a timeout
-  optional bool association_timed_out = 12 [default = false];
-
-  // Authentication failure reason, as reported by WifiManager (calculated from state & deauth code)
-  optional AuthFailureReason auth_failure_reason = 13 [default = AUTH_FAILURE_UNKNOWN];
-
-  // NetworkAgent score of connected wifi
-  optional int32 last_score = 14 [default = -1];
-
-  // NetworkAgent Wifi usability score of connected wifi
-  optional int32 last_wifi_usability_score = 15 [default = -1];
-
-  // Prediction horizon (in second) of Wifi usability score provided by external
-  // system app
-  optional int32 last_prediction_horizon_sec = 16 [default = -1];
-
-  // Only valid if event type == TYPE_LINK_PROBE.
-  // true if link probe succeeded, false otherwise.
-  optional bool link_probe_was_success = 17;
-
-  // Only valid if event type == TYPE_LINK_PROBE and link_probe_was_success == true.
-  // Elapsed time, in milliseconds, of a successful link probe.
-  optional int32 link_probe_success_elapsed_time_ms = 18;
-
-  // Only valid if event type == TYPE_LINK_PROBE and link_probe_was_success == false.
-  // Failure reason for an unsuccessful link probe.
-  optional LinkProbeStats.LinkProbeFailureReason link_probe_failure_reason = 19;
-}
-
-// Wi-Fi Aware metrics
-message WifiAwareLog {
-  // total number of unique apps that used Aware (measured on attach)
-  optional int32 num_apps = 1;
-
-  // total number of unique apps that used an identity callback when attaching
-  optional int32 num_apps_using_identity_callback = 2;
-
-  // maximum number of attaches for an app
-  optional int32 max_concurrent_attach_sessions_in_app = 3;
-
-  // histogram of attach request results
-  repeated NanStatusHistogramBucket histogram_attach_session_status = 4;
-
-  // maximum number of concurrent publish sessions in a single app
-  optional int32 max_concurrent_publish_in_app = 5;
-
-  // maximum number of concurrent subscribe sessions in a single app
-  optional int32 max_concurrent_subscribe_in_app = 6;
-
-  // maximum number of concurrent discovery (publish+subscribe) sessions in a single app
-  optional int32 max_concurrent_discovery_sessions_in_app = 7;
-
-  // maximum number of concurrent publish sessions in the system
-  optional int32 max_concurrent_publish_in_system = 8;
-
-  // maximum number of concurrent subscribe sessions in the system
-  optional int32 max_concurrent_subscribe_in_system = 9;
-
-  // maximum number of concurrent discovery (publish+subscribe) sessions in the system
-  optional int32 max_concurrent_discovery_sessions_in_system = 10;
-
-  // histogram of publish request results
-  repeated NanStatusHistogramBucket histogram_publish_status = 11;
-
-  // histogram of subscribe request results
-  repeated NanStatusHistogramBucket histogram_subscribe_status = 12;
-
-  // number of unique apps which experienced a discovery session creation failure due to lack of
-  // resources
-  optional int32 num_apps_with_discovery_session_failure_out_of_resources = 13;
-
-  // histogram of create ndp request results
-  repeated NanStatusHistogramBucket histogram_request_ndp_status = 14;
-
-  // histogram of create ndp out-of-band (OOB) request results
-  repeated NanStatusHistogramBucket histogram_request_ndp_oob_status = 15;
-
-  // maximum number of concurrent active data-interfaces (NDI) in a single app
-  optional int32 max_concurrent_ndi_in_app = 19;
-
-  // maximum number of concurrent active data-interfaces (NDI) in the system
-  optional int32 max_concurrent_ndi_in_system = 20;
-
-  // maximum number of concurrent data-paths (NDP) in a single app
-  optional int32 max_concurrent_ndp_in_app = 21;
-
-  // maximum number of concurrent data-paths (NDP) in the system
-  optional int32 max_concurrent_ndp_in_system = 22;
-
-  // maximum number of concurrent secure data-paths (NDP) in a single app
-  optional int32 max_concurrent_secure_ndp_in_app = 23;
-
-  // maximum number of concurrent secure data-paths (NDP) in the system
-  optional int32 max_concurrent_secure_ndp_in_system = 24;
-
-  // maximum number of concurrent data-paths (NDP) per data-interface (NDI)
-  optional int32 max_concurrent_ndp_per_ndi = 25;
-
-  // histogram of durations of Aware being available
-  repeated HistogramBucket histogram_aware_available_duration_ms = 26;
-
-  // histogram of durations of Aware being enabled
-  repeated HistogramBucket histogram_aware_enabled_duration_ms = 27;
-
-  // histogram of duration (in ms) of attach sessions
-  repeated HistogramBucket histogram_attach_duration_ms = 28;
-
-  // histogram of duration (in ms) of publish sessions
-  repeated HistogramBucket histogram_publish_session_duration_ms = 29;
-
-  // histogram of duration (in ms) of subscribe sessions
-  repeated HistogramBucket histogram_subscribe_session_duration_ms = 30;
-
-  // histogram of duration (in ms) of data-paths (NDP)
-  repeated HistogramBucket histogram_ndp_session_duration_ms = 31;
-
-  // histogram of usage (in MB) of data-paths (NDP)
-  repeated HistogramBucket histogram_ndp_session_data_usage_mb = 32;
-
-  // histogram of usage (in MB) of data-path creation time (in ms) measured as request -> confirm
-  repeated HistogramBucket histogram_ndp_creation_time_ms = 33;
-
-  // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: minimum
-  optional int64 ndp_creation_time_ms_min = 34;
-
-  // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: maximum
-  optional int64 ndp_creation_time_ms_max = 35;
-
-  // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: sum
-  optional int64 ndp_creation_time_ms_sum = 36;
-
-  // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: sum of sq
-  optional int64 ndp_creation_time_ms_sum_of_sq = 37;
-
-  // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: number of
-  // samples
-  optional int64 ndp_creation_time_ms_num_samples = 38;
-
-  // total time within the logging window that aware was available
-  optional int64 available_time_ms = 39;
-
-  // total time within the logging window that aware was enabled
-  optional int64 enabled_time_ms = 40;
-
-  // maximum number of concurrent publish sessions enabling ranging in a single app
-  optional int32 max_concurrent_publish_with_ranging_in_app = 41;
-
-  // maximum number of concurrent subscribe sessions specifying a geofence in a single app
-  optional int32 max_concurrent_subscribe_with_ranging_in_app = 42;
-
-  // maximum number of concurrent publish sessions enabling ranging in the system
-  optional int32 max_concurrent_publish_with_ranging_in_system = 43;
-
-  // maximum number of concurrent subscribe sessions specifying a geofence in the system
-  optional int32 max_concurrent_subscribe_with_ranging_in_system = 44;
-
-  // histogram of subscribe session geofence minimum (only when specified)
-  repeated HistogramBucket histogram_subscribe_geofence_min = 45;
-
-  // histogram of subscribe session geofence maximum (only when specified)
-  repeated HistogramBucket histogram_subscribe_geofence_max = 46;
-
-  // total number of subscribe sessions which enabled ranging
-  optional int32 num_subscribes_with_ranging = 47;
-
-  // total number of matches (service discovery indication) with ranging provided
-  optional int32 num_matches_with_ranging = 48;
-
-  // total number of matches (service discovery indication) for service discovery with ranging
-  // enabled which did not trigger ranging
-  optional int32 num_matches_without_ranging_for_ranging_enabled_subscribes = 49;
-
-  // Histogram bucket for Wi-Fi Aware logs. Range is [start, end)
-  message HistogramBucket {
-    // lower range of the bucket (inclusive)
-    optional int64 start = 1;
-
-    // upper range of the bucket (exclusive)
-    optional int64 end = 2;
-
-    // number of samples in the bucket
-    optional int32 count = 3;
-  }
-
-  // Status of various NAN operations
-  enum NanStatusTypeEnum {
-    // constant to be used by proto
-    UNKNOWN = 0;
-
-    // NAN operation succeeded
-    SUCCESS = 1;
-
-    // NAN Discovery Engine/Host driver failures
-    INTERNAL_FAILURE = 2;
-
-    // NAN OTA failures
-    PROTOCOL_FAILURE = 3;
-
-    // The publish/subscribe discovery session id is invalid
-    INVALID_SESSION_ID = 4;
-
-    // Out of resources to fufill request
-    NO_RESOURCES_AVAILABLE = 5;
-
-    // Invalid arguments passed
-    INVALID_ARGS = 6;
-
-    // Invalid peer id
-    INVALID_PEER_ID = 7;
-
-    // Invalid NAN data-path (ndp) id
-    INVALID_NDP_ID = 8;
-
-    // Attempting to enable NAN when not available, e.g. wifi is disabled
-    NAN_NOT_ALLOWED = 9;
-
-    // Over the air ACK not received
-    NO_OTA_ACK = 10;
-
-    // Attempting to enable NAN when already enabled
-    ALREADY_ENABLED = 11;
-
-    // Can't queue tx followup message foor transmission
-    FOLLOWUP_TX_QUEUE_FULL = 12;
-
-    // Unsupported concurrency of NAN and another feature - NAN disabled
-    UNSUPPORTED_CONCURRENCY_NAN_DISABLED = 13;
-
-    // Unknown NanStatusType
-    UNKNOWN_HAL_STATUS = 14;
-  }
-
-  // Histogram bucket for Wi-Fi Aware (NAN) status.
-  message NanStatusHistogramBucket {
-    // status type defining the bucket
-    optional NanStatusTypeEnum nan_status_type = 1;
-
-    // number of samples in the bucket
-    optional int32 count = 2;
-  }
-}
-
-// Data point used to build 'Number of Connectable Network' histograms
-message NumConnectableNetworksBucket {
-  // Number of connectable networks seen in a scan result
-  optional int32 num_connectable_networks = 1 [default = 0];
-
-  // Number of scan results with num_connectable_networks
-  optional int32 count = 2 [default = 0];
-}
-
-// Pno scan metrics
-// Here "Pno Scan" refers to the session of offloaded scans, these metrics count the result of a
-// single session, and not the individual scans within that session.
-message PnoScanMetrics {
-  // Total number of attempts to offload pno scans
-  optional int32 num_pno_scan_attempts = 1;
-
-  // Total number of pno scans failed
-  optional int32 num_pno_scan_failed = 2;
-
-  // Number of pno scans started successfully over offload
-  optional int32 num_pno_scan_started_over_offload = 3;
-
-  // Number of pno scans failed over offload
-  optional int32 num_pno_scan_failed_over_offload = 4;
-
-  // Total number of pno scans that found any network
-  optional int32 num_pno_found_network_events = 5;
-}
-
-// Number of occurrences for a particular "Connect to Network" Notification or
-// notification Action.
-message ConnectToNetworkNotificationAndActionCount {
-
-  // "Connect to Network" notifications
-  enum Notification {
-
-    // Default
-    NOTIFICATION_UNKNOWN = 0;
-
-    // Initial notification with a recommended network.
-    NOTIFICATION_RECOMMEND_NETWORK = 1;
-
-    // Notification when connecting to the recommended network.
-    NOTIFICATION_CONNECTING_TO_NETWORK = 2;
-
-    // Notification when successfully connected to the network.
-    NOTIFICATION_CONNECTED_TO_NETWORK = 3;
-
-    // Notification when failed to connect to network.
-    NOTIFICATION_FAILED_TO_CONNECT = 4;
-  }
-
-  // "Connect to Network" notification actions
-  enum Action {
-
-    // Default
-    ACTION_UNKNOWN = 0;
-
-    // User dismissed the "Connect to Network" notification.
-    ACTION_USER_DISMISSED_NOTIFICATION = 1;
-
-    // User tapped action button to connect to recommended network.
-    ACTION_CONNECT_TO_NETWORK = 2;
-
-    // User tapped action button to open Wi-Fi Settings.
-    ACTION_PICK_WIFI_NETWORK = 3;
-
-    // User tapped "Failed to connect" notification to open Wi-Fi Settings.
-    ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE = 4;
-  }
-
-  // Recommenders of the "Connect to Network" notification
-  enum Recommender {
-
-    // Default.
-    RECOMMENDER_UNKNOWN = 0;
-
-    // Open Network Available recommender.
-    RECOMMENDER_OPEN = 1;
-  }
-
-  // Notification Type.
-  optional Notification notification = 1;
-
-  // Action Type.
-  optional Action action = 2;
-
-  // Recommender Type.
-  optional Recommender recommender = 3;
-
-  // Occurrences of this action.
-  optional int32 count = 4;
-}
-
-// SoftAP event tracking sessions and client counts
-message SoftApConnectedClientsEvent {
-
-  // Soft AP event Types
-  enum SoftApEventType {
-
-    // Soft AP is Up and ready for use
-    SOFT_AP_UP = 0;
-
-    // Soft AP is Down
-    SOFT_AP_DOWN = 1;
-
-    // Number of connected soft AP clients has changed
-    NUM_CLIENTS_CHANGED = 2;
-  }
-
-  // Soft AP channel bandwidth types
-  enum ChannelBandwidth {
-
-    BANDWIDTH_INVALID = 0;
-
-    BANDWIDTH_20_NOHT = 1;
-
-    BANDWIDTH_20 = 2;
-
-    BANDWIDTH_40 = 3;
-
-    BANDWIDTH_80 = 4;
-
-    BANDWIDTH_80P80 = 5;
-
-    BANDWIDTH_160 = 6;
-  }
-
-  // Type of event being recorded
-  optional SoftApEventType event_type = 1;
-
-  // Time passed since last boot in milliseconds
-  optional int64 time_stamp_millis = 2;
-
-  // Number of connected clients if event_type is NUM_CLIENTS_CHANGED, otherwise zero.
-  optional int32 num_connected_clients = 3;
-
-  // Channel frequency used for Soft AP
-  optional int32 channel_frequency = 4;
-
-  // Channel bandwidth used for Soft AP
-  optional ChannelBandwidth channel_bandwidth = 5;
-}
-
-// Wps connection metrics
-// Keeps track of Wi-Fi Protected Setup usage
-message WpsMetrics {
-  // Total number of wps connection attempts
-  optional int32 num_wps_attempts = 1;
-
-  // Total number of wps connection successes
-  optional int32 num_wps_success = 2;
-
-  // Total number of wps failures on start
-  optional int32 num_wps_start_failure = 3;
-
-  // Total number of wps overlap failure
-  optional int32 num_wps_overlap_failure = 4;
-
-  // Total number of wps timeout failure
-  optional int32 num_wps_timeout_failure = 5;
-
-  // Total number of other wps failure during connection
-  optional int32 num_wps_other_connection_failure = 6;
-
-  // Total number of supplicant failure after wps
-  optional int32 num_wps_supplicant_failure = 7;
-
-  // Total number of wps cancellation
-  optional int32 num_wps_cancellation = 8;
-}
-
-// Power stats for Wifi
-message WifiPowerStats {
-
-  // Duration of log (ms)
-  optional int64 logging_duration_ms = 1;
-
-  // Energy consumed by wifi (mAh)
-  optional double energy_consumed_mah = 2;
-
-  // Amount of time wifi is in idle (ms)
-  optional int64 idle_time_ms = 3;
-
-  // Amount of time wifi is in rx (ms)
-  optional int64 rx_time_ms = 4;
-
-  // Amount of time wifi is in tx (ms)
-  optional int64 tx_time_ms = 5;
-
-  // Amount of time kernel is active because of wifi data (ms)
-  optional int64 wifi_kernel_active_time_ms = 6;
-
-  // Number of packets sent (tx)
-  optional int64 num_packets_tx = 7;
-
-  // Number of bytes sent (tx)
-  optional int64 num_bytes_tx = 8;
-
-  // Number of packets received (rx)
-  optional int64 num_packets_rx = 9;
-
-  // Number of bytes sent (rx)
-  optional int64 num_bytes_rx = 10;
-
-  // Amount of time wifi is in sleep (ms)
-  optional int64 sleep_time_ms = 11;
-
-  // Amount of time wifi is scanning (ms)
-  optional int64 scan_time_ms = 12;
-
-  // Actual monitored rail energy consumed by wifi (mAh)
-  optional double monitored_rail_energy_consumed_mah = 13;
-}
-
-// Metrics for Wifi Wake
-message WifiWakeStats {
-  // An individual session for Wifi Wake
-  message Session {
-    // A Wifi Wake lifecycle event
-    message Event {
-      // Elapsed time in milliseconds since start of session.
-      optional int64 elapsed_time_millis = 1;
-
-      // Number of scans that have occurred since start of session.
-      optional int32 elapsed_scans = 2;
-    }
-
-    // Start time of session in milliseconds.
-    optional int64 start_time_millis = 1;
-
-    // The number of networks the lock was provided with at start.
-    optional int32 locked_networks_at_start = 2;
-
-    // The number of networks in the lock at the time of the initialize event. Only valid if
-    // initialize_event is recorded.
-    optional int32 locked_networks_at_initialize = 6;
-
-    // Event for fully initializing the WakeupLock (i.e. WakeupLock is "locked").
-    optional Event initialize_event = 7;
-
-    // Event for unlocking the WakeupLock. Does not occur if lock was initialized with 0 networks.
-    optional Event unlock_event = 3;
-
-    // Event for triggering wakeup.
-    optional Event wakeup_event = 4;
-
-    // Event for WifiWake reset event. This event marks the end of a session.
-    optional Event reset_event = 5;
-  }
-
-  // Total number of sessions for Wifi Wake.
-  optional int32 num_sessions = 1;
-
-  // Session information for every Wifi Wake session (up to a maximum of 10).
-  repeated Session sessions = 2;
-
-  // Number of ignored calls to start (due to WakeupController already being active).
-  optional int32 num_ignored_starts = 3;
-
-  // Number of Wifi Wake sessions that have recorded wakeup events.
-  optional int32 num_wakeups = 4;
-}
-
-// Metrics for Wi-Fi RTT
-message WifiRttLog {
-  // Number of RTT request API calls
-  optional int32 num_requests = 1;
-
-  // Histogram of RTT operation overall status
-  repeated RttOverallStatusHistogramBucket histogram_overall_status = 2;
-
-  // RTT to Access Points metrics
-  optional RttToPeerLog rtt_to_ap = 3;
-
-  // RTT to Wi-Fi Aware peers metrics
-  optional RttToPeerLog rtt_to_aware = 4;
-
-  // Metrics for a RTT to Peer (peer = AP or Wi-Fi Aware)
-  message RttToPeerLog {
-    // Total number of API calls
-    optional int32 num_requests = 1;
-
-    // Total number of individual requests
-    optional int32 num_individual_requests = 2;
-
-    // Total number of apps which requested RTT
-    optional int32 num_apps = 3;
-
-    // Histogram of total number of RTT requests by an app (WifiRttManager#startRanging)
-    repeated HistogramBucket histogram_num_requests_per_app = 4;
-
-    // Histogram of number of peers in a single RTT request (RangingRequest entries)
-    repeated HistogramBucket histogram_num_peers_per_request = 5;
-
-    // Histogram of status of individual RTT operations (RangingResult entries)
-    repeated RttIndividualStatusHistogramBucket histogram_individual_status = 6;
-
-    // Histogram of measured distances (RangingResult entries)
-    repeated HistogramBucket histogram_distance = 7;
-
-    // Histogram of interval of RTT requests by an app (WifiRttManager#startRanging)
-    repeated HistogramBucket histogram_request_interval_ms = 8;
-  }
-
-  // Histogram bucket for Wi-Fi RTT logs. Range is [start, end)
-  message HistogramBucket {
-    // lower range of the bucket (inclusive)
-    optional int64 start = 1;
-
-    // upper range of the bucket (exclusive)
-    optional int64 end = 2;
-
-    // number of samples in the bucket
-    optional int32 count = 3;
-  }
-
-  // Status codes for overall RTT operation
-  enum RttOverallStatusTypeEnum {
-    // constant to be used by proto
-    OVERALL_UNKNOWN = 0;
-
-    // RTT operation succeeded (individual results may still fail)
-    OVERALL_SUCCESS = 1;
-
-    // RTT operation failed (unspecified reason)
-    OVERALL_FAIL = 2;
-
-    // RTT operation failed since RTT was not available (e.g. Airplane mode)
-    OVERALL_RTT_NOT_AVAILABLE = 3;
-
-    // RTT operation timed-out: didn't receive response from HAL in expected time
-    OVERALL_TIMEOUT = 4;
-
-    // RTT operation aborted since the app is spamming the service
-    OVERALL_THROTTLE = 5;
-
-    // RTT request to HAL received immediate failure
-    OVERALL_HAL_FAILURE = 6;
-
-    // RTT to Wi-Fi Aware peer using PeerHandle failed to get a MAC address translation
-    OVERALL_AWARE_TRANSLATION_FAILURE = 7;
-
-    // RTT operation failed due to missing Location permission (post execution)
-    OVERALL_LOCATION_PERMISSION_MISSING = 8;
-  }
-
-  // Status codes for individual RTT operation
-  enum RttIndividualStatusTypeEnum {
-    // constant to be used by proto
-    UNKNOWN = 0;
-
-    // RTT operation succeeded
-    SUCCESS = 1;
-
-    // RTT failure: generic reason (no further information)
-    FAILURE = 2;
-
-    // Target STA does not respond to request
-    FAIL_NO_RSP = 3;
-
-    // Request rejected. Applies to 2-sided RTT only
-    FAIL_REJECTED = 4;
-
-    // Operation not scheduled
-    FAIL_NOT_SCHEDULED_YET = 5;
-
-    // Timing measurement times out
-    FAIL_TM_TIMEOUT = 6;
-
-    // Target on different channel, cannot range
-    FAIL_AP_ON_DIFF_CHANNEL = 7;
-
-    // Ranging not supported
-    FAIL_NO_CAPABILITY = 8;
-
-    // Request aborted for unknown reason
-    ABORTED = 9;
-
-    // Invalid T1-T4 timestamp
-    FAIL_INVALID_TS = 10;
-
-    // 11mc protocol failed
-    FAIL_PROTOCOL = 11;
-
-    // Request could not be scheduled
-    FAIL_SCHEDULE = 12;
-
-    // Responder cannot collaborate at time of request
-    FAIL_BUSY_TRY_LATER = 13;
-
-    // Bad request args
-    INVALID_REQ = 14;
-
-    // WiFi not enabled
-    NO_WIFI = 15;
-
-    // Responder overrides param info, cannot range with new params
-    FAIL_FTM_PARAM_OVERRIDE = 16;
-
-    // HAL did not provide a result to a framework request
-    MISSING_RESULT = 17;
-  }
-
-  // Histogram bucket for Wi-Fi RTT overall operation status
-  message RttOverallStatusHistogramBucket {
-    // status type defining the bucket
-    optional RttOverallStatusTypeEnum status_type = 1;
-
-    // number of samples in the bucket
-    optional int32 count = 2;
-  }
-
-  // Histogram bucket for Wi-Fi RTT individual operation status
-  message RttIndividualStatusHistogramBucket {
-    // status type defining the bucket
-    optional RttIndividualStatusTypeEnum status_type = 1;
-
-    // number of samples in the bucket
-    optional int32 count = 2;
-  }
-}
-
-// Usage data for the wifi radio while device is running on battery.
-message WifiRadioUsage {
-  // Duration of log (ms)
-  optional int64 logging_duration_ms = 1;
-
-  // Total time for which the radio is awake due to scan.
-  optional int64 scan_time_ms = 2;
-}
-
-message ExperimentValues {
-  // Indicates if we are logging WifiIsUnusableEvent in metrics
-  optional bool wifi_is_unusable_logging_enabled = 1;
-
-  // Minimum number of txBad to trigger a data stall
-  optional int32 wifi_data_stall_min_tx_bad = 2;
-
-  // Minimum number of txSuccess to trigger a data stall
-  // when rxSuccess is 0
-  optional int32 wifi_data_stall_min_tx_success_without_rx = 3;
-
-  // Indicates if we are logging LinkSpeedCount in metrics
-  optional bool link_speed_counts_logging_enabled = 4;
-
-  // Duration for evaluating Wifi condition to trigger a data stall
-  // measured in milliseconds
-  optional int32 data_stall_duration_ms = 5;
-
-  // Threshold of Tx throughput below which to trigger a data stall
-  // measured in Kbps
-  optional int32 data_stall_tx_tput_thr_kbps = 6;
-
-  // Threshold of Rx throughput below which to trigger a data stall
-  // measured in Kbps
-  optional int32 data_stall_rx_tput_thr_kbps = 7;
-
-  // Threshold of Tx packet error rate above which to trigger a data stall
-  // in percentage
-  optional int32 data_stall_tx_per_thr = 8;
-
-  // Threshold of CCA level above which to trigger a data stall in percentage
-  optional int32 data_stall_cca_level_thr = 9;
-}
-
-message WifiIsUnusableEvent {
-  enum TriggerType {
-    // Default/Invalid event
-    TYPE_UNKNOWN = 0;
-
-    // There is a data stall from tx failures
-    TYPE_DATA_STALL_BAD_TX = 1;
-
-    // There is a data stall from rx failures
-    TYPE_DATA_STALL_TX_WITHOUT_RX = 2;
-
-    // There is a data stall from both tx and rx failures
-    TYPE_DATA_STALL_BOTH = 3;
-
-    // Firmware generated an alert
-    TYPE_FIRMWARE_ALERT = 4;
-
-    // IP Manager lost reachability to network neighbors
-    TYPE_IP_REACHABILITY_LOST = 5;
-  }
-
-  // What event triggered WifiIsUnusableEvent.
-  optional TriggerType type = 1;
-
-  // The timestamp at which this event occurred.
-  // Measured in milliseconds that have elapsed since the device booted.
-  optional int64 start_time_millis = 2;
-
-  // NetworkAgent score of connected wifi.
-  // Defaults to -1 if the score was never set.
-  optional int32 last_score = 3 [default = -1];
-
-  // Delta of successfully transmitted (ACKed) unicast data packets
-  // between the last two WifiLinkLayerStats.
-  optional int64 tx_success_delta = 4;
-
-  // Delta of transmitted unicast data retry packets
-  // between the last two WifiLinkLayerStats.
-  optional int64 tx_retries_delta = 5;
-
-  // Delta of lost (not ACKed) transmitted unicast data packets
-  // between the last two WifiLinkLayerStats.
-  optional int64 tx_bad_delta = 6;
-
-  // Delta of received unicast data packets
-  // between the last two WifiLinkLayerStats.
-  optional int64 rx_success_delta = 7;
-
-  // Time in millisecond between the last two WifiLinkLayerStats.
-  optional int64 packet_update_time_delta = 8;
-
-  // The timestamp at which the last WifiLinkLayerStats was updated.
-  // Measured in milliseconds that have elapsed since the device booted.
-  optional int64 last_link_layer_stats_update_time = 9;
-
-  // Firmware alert code. Only valid when the event was triggered by a firmware alert, otherwise -1.
-  optional int32 firmware_alert_code = 10 [default = -1];
-
-  // NetworkAgent wifi usability score of connected wifi.
-  // Defaults to -1 if the score was never set.
-  optional int32 last_wifi_usability_score = 11 [default = -1];
-
-  // Prediction horizon (in second) of Wifi usability score provided by external
-  // system app
-  optional int32 last_prediction_horizon_sec = 12 [default = -1];
-
-  // Whether screen status is on when WifiIsUnusableEvent happens.
-  optional bool screen_on = 13 [default = false];
-}
-
-message PasspointProfileTypeCount {
-  enum EapMethod {
-    // Unknown Type
-    TYPE_UNKNOWN = 0;
-
-    // EAP_TLS (13)
-    TYPE_EAP_TLS = 1;
-
-    // EAP_TTLS (21)
-    TYPE_EAP_TTLS = 2;
-
-    // EAP_SIM (18)
-    TYPE_EAP_SIM = 3;
-
-    // EAP_AKA (23)
-    TYPE_EAP_AKA = 4;
-
-    // EAP_AKA_PRIME (50)
-    TYPE_EAP_AKA_PRIME = 5;
-  }
-
-  // Eap method type set in Passpoint profile
-  optional EapMethod eap_method_type = 1;
-
-  // Num of installed Passpoint profile with same eap method
-  optional int32 count = 2;
-}
-
-message WifiLinkLayerUsageStats {
-  // Total logging duration in ms.
-  optional int64 logging_duration_ms = 1;
-
-  // Total time the wifi radio is on in ms over the logging duration.
-  optional int64 radio_on_time_ms = 2;
-
-  // Total time the wifi radio is doing tx in ms over the logging duration.
-  optional int64 radio_tx_time_ms = 3;
-
-  // Total time the wifi radio is doing rx in ms over the logging duration.
-  optional int64 radio_rx_time_ms = 4;
-
-  // Total time the wifi radio is scanning in ms over the logging duration.
-  optional int64 radio_scan_time_ms = 5;
-
-  // Total time the wifi radio spent doing nan scans in ms over the logging duration.
-  optional int64 radio_nan_scan_time_ms = 6;
-
-  // Total time the wifi radio spent doing background scans in ms over the logging duration.
-  optional int64 radio_background_scan_time_ms = 7;
-
-  // Total time the wifi radio spent doing roam scans in ms over the logging duration.
-  optional int64 radio_roam_scan_time_ms = 8;
-
-  // Total time the wifi radio spent doing pno scans in ms over the logging duration.
-  optional int64 radio_pno_scan_time_ms = 9;
-
-  // Total time the wifi radio spent doing hotspot 2.0 scans and GAS exchange
-  // in ms over the logging duration.
-  optional int64 radio_hs20_scan_time_ms = 10;
-}
-
-message WifiUsabilityStatsEntry {
-  // Status codes for link probe status
-  enum LinkProbeStatus {
-    // Link probe status is unknown
-    PROBE_STATUS_UNKNOWN = 0;
-
-    // Link probe is not triggered
-    PROBE_STATUS_NO_PROBE = 1;
-
-    // Link probe is triggered and the result is success
-    PROBE_STATUS_SUCCESS = 2;
-
-    // Link probe is triggered and the result is failure
-    PROBE_STATUS_FAILURE = 3;
-  }
-
-  // Codes for cellular data network type
-  enum CellularDataNetworkType {
-    // Unknown network
-    NETWORK_TYPE_UNKNOWN = 0;
-
-    // GSM network
-    NETWORK_TYPE_GSM = 1;
-
-    // CDMA network
-    NETWORK_TYPE_CDMA = 2;
-
-    // CDMA EVDO network
-    NETWORK_TYPE_EVDO_0 = 3;
-
-    // WCDMA network
-    NETWORK_TYPE_UMTS = 4;
-
-    // TDSCDMA network
-    NETWORK_TYPE_TD_SCDMA = 5;
-
-    // LTE network
-    NETWORK_TYPE_LTE = 6;
-
-    // NR network
-    NETWORK_TYPE_NR = 7;
-  }
-
-  // Absolute milliseconds from device boot when these stats were sampled
-  optional int64 time_stamp_ms = 1;
-
-  // The RSSI at the sample time
-  optional int32 rssi = 2;
-
-  // Link speed at the sample time in Mbps
-  optional int32 link_speed_mbps = 3;
-
-  // The total number of tx success counted from the last radio chip reset
-  optional int64 total_tx_success = 4;
-
-  // The total number of MPDU data packet retries counted from the last radio chip reset
-  optional int64 total_tx_retries = 5;
-
-  // The total number of tx bad counted from the last radio chip reset
-  optional int64 total_tx_bad = 6;
-
-  // The total number of rx success counted from the last radio chip reset
-  optional int64 total_rx_success = 7;
-
-  // The total time the wifi radio is on in ms counted from the last radio chip reset
-  optional int64 total_radio_on_time_ms = 8;
-
-  // The total time the wifi radio is doing tx in ms counted from the last radio chip reset
-  optional int64 total_radio_tx_time_ms = 9;
-
-  // The total time the wifi radio is doing rx in ms counted from the last radio chip reset
-  optional int64 total_radio_rx_time_ms = 10;
-
-  // The total time spent on all types of scans in ms counted from the last radio chip reset
-  optional int64 total_scan_time_ms = 11;
-
-  // The total time spent on nan scans in ms counted from the last radio chip reset
-  optional int64 total_nan_scan_time_ms = 12;
-
-  // The total time spent on background scans in ms counted from the last radio chip reset
-  optional int64 total_background_scan_time_ms = 13;
-
-  // The total time spent on roam scans in ms counted from the last radio chip reset
-  optional int64 total_roam_scan_time_ms = 14;
-
-  // The total time spent on pno scans in ms counted from the last radio chip reset
-  optional int64 total_pno_scan_time_ms = 15;
-
-  // The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
-  // chip reset
-  optional int64 total_hotspot_2_scan_time_ms = 16;
-
-  // Internal framework Wifi score
-  optional int32 wifi_score = 17;
-
-  // Wifi usability score provided by external system app
-  optional int32 wifi_usability_score = 18;
-
-  // Sequence number from external system app to framework
-  optional int32 seq_num_to_framework = 19;
-
-  // The total time CCA is on busy status on the current frequency in ms
-  // counted from the last radio chip reset
-  optional int64 total_cca_busy_freq_time_ms = 20;
-
-  // The total radio on time of the current frequency from the last radio
-  // chip reset
-  optional int64 total_radio_on_freq_time_ms = 21;
-
-  // The total number of beacons received from the last radio chip reset
-  optional int64 total_beacon_rx = 22;
-
-  // Prediction horizon (in second) of Wifi usability score provided by external
-  // system app
-  optional int32 prediction_horizon_sec = 23;
-
-  // The link probe status since last stats update
-  optional LinkProbeStatus probe_status_since_last_update = 24;
-
-  // The elapsed time of the most recent link probe since last stats update;
-  optional int32 probe_elapsed_time_since_last_update_ms = 25;
-
-  // The MCS rate of the most recent link probe since last stats update
-  optional int32 probe_mcs_rate_since_last_update = 26;
-
-  // Rx link speed at the sample time in Mbps
-  optional int32 rx_link_speed_mbps = 27;
-
-  // Sequence number generated by framework
-  optional int32 seq_num_inside_framework = 28;
-
-  // Whether current entry is for the same BSSID on the same frequency compared
-  // to last entry
-  optional bool is_same_bssid_and_freq = 29;
-
-  // Cellular data network type currently in use on the device for data transmission
-  optional CellularDataNetworkType cellular_data_network_type = 30;
-
-  // Cellular signal strength in dBm, NR: CsiRsrp, LTE: Rsrp, WCDMA/TDSCDMA: Rscp,
-  // CDMA: Rssi, EVDO: Rssi, GSM: Rssi
-  optional int32 cellular_signal_strength_dbm = 31;
-
-  // Cellular signal strength in dB, NR: CsiSinr, LTE: Rsrq, WCDMA: EcNo, TDSCDMA: invalid,
-  // CDMA: Ecio, EVDO: SNR, GSM: invalid */
-  optional int32 cellular_signal_strength_db = 32;
-
-  // Whether the primary registered cell of current entry is same as that of previous entry
-  optional bool is_same_registered_cell = 33;
-
-  // The device mobility state
-  optional DeviceMobilityStatePnoScanStats.DeviceMobilityState
-          device_mobility_state = 34;
-}
-
-message WifiUsabilityStats {
-  enum Label {
-    // Default label
-    LABEL_UNKNOWN = 0;
-
-    // Wifi is usable
-    LABEL_GOOD = 1;
-
-    // Wifi is unusable
-    LABEL_BAD = 2;
-  }
-
-  enum UsabilityStatsTriggerType {
-    // Default/Invalid event
-    TYPE_UNKNOWN = 0;
-
-    // There is a data stall from tx failures
-    TYPE_DATA_STALL_BAD_TX = 1;
-
-    // There is a data stall from rx failures
-    TYPE_DATA_STALL_TX_WITHOUT_RX = 2;
-
-    // There is a data stall from both tx and rx failures
-    TYPE_DATA_STALL_BOTH = 3;
-
-    // Firmware generated an alert
-    TYPE_FIRMWARE_ALERT = 4;
-
-    // IP Manager lost reachability to network neighbors
-    TYPE_IP_REACHABILITY_LOST = 5;
-  }
-
-  // The current wifi usability state
-  optional Label label = 1;
-
-  // The list of timestamped wifi usability stats
-  repeated WifiUsabilityStatsEntry stats = 2;
-
-  // What event triggered WifiUsabilityStats.
-  optional UsabilityStatsTriggerType trigger_type = 3;
-
-  // Firmware alert code. Only valid when the stats was triggered by a firmware
-  // alert, otherwise -1.
-  optional int32 firmware_alert_code = 4 [default = -1];
-
-  // Absolute milliseconds from device boot when these stats were sampled
-  optional int64 time_stamp_ms = 5;
-}
-
-message DeviceMobilityStatePnoScanStats {
-  // see WifiManager.DEVICE_MOBILITY_STATE_* constants
-  enum DeviceMobilityState {
-    // Unknown mobility
-    UNKNOWN = 0;
-
-    // High movement
-    HIGH_MVMT = 1;
-
-    // Low movement
-    LOW_MVMT = 2;
-
-    // Stationary
-    STATIONARY = 3;
-  }
-
-  // The device mobility state
-  optional DeviceMobilityState device_mobility_state = 1;
-
-  // The number of times that this state was entered
-  optional int32 num_times_entered_state = 2;
-
-  // The total duration elapsed while in this mobility state, in ms
-  optional int64 total_duration_ms = 3;
-
-  // the total duration elapsed while in this mobility state with PNO scans running, in ms
-  optional int64 pno_duration_ms = 4;
-}
-
-// The information about the Wifi P2p events.
-message WifiP2pStats {
-
-  // Group event list tracking sessions and client counts in tethered mode.
-  repeated GroupEvent group_event = 1;
-
-  // Session information that gets logged for every Wifi P2p connection.
-  repeated P2pConnectionEvent connection_event = 2;
-
-  // Number of persistent group in the user profile.
-  optional int32 num_persistent_group = 3;
-
-  // Number of peer scan.
-  optional int32 num_total_peer_scans = 4;
-
-  // Number of service scan.
-  optional int32 num_total_service_scans = 5;
-}
-
-message P2pConnectionEvent {
-
-  enum ConnectionType {
-
-    // fresh new connection.
-    CONNECTION_FRESH = 0;
-
-    // reinvoke a group.
-    CONNECTION_REINVOKE = 1;
-
-    // create a group with the current device as the group owner locally.
-    CONNECTION_LOCAL = 2;
-
-    // create a group or join a group with config.
-    CONNECTION_FAST = 3;
-  }
-
-  enum ConnectivityLevelFailure {
-
-    // Failure is unknown.
-    CLF_UNKNOWN = 0;
-
-    // No failure.
-    CLF_NONE = 1;
-
-    // Timeout for current connecting request.
-    CLF_TIMEOUT = 2;
-
-    // The connecting request is canceled by the user.
-    CLF_CANCEL = 3;
-
-    // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
-    CLF_PROV_DISC_FAIL = 4;
-
-    // Invitation failure, e.g. rejected by the peer.
-    CLF_INVITATION_FAIL = 5;
-
-    // Incoming request is rejected by the user.
-    CLF_USER_REJECT = 6;
-
-    // New connection request is issued before ending previous connecting request.
-    CLF_NEW_CONNECTION_ATTEMPT = 7;
-  }
-
-  // WPS method.
-  enum WpsMethod {
-    // WPS is skipped for Group Reinvoke.
-    WPS_NA = -1;
-
-    // Push button configuration.
-    WPS_PBC = 0;
-
-    // Display pin method configuration - pin is generated and displayed on device.
-    WPS_DISPLAY = 1;
-
-    // Keypad pin method configuration - pin is entered on device.
-    WPS_KEYPAD = 2;
-
-    // Label pin method configuration - pin is labelled on device.
-    WPS_LABEL = 3;
-  }
-
-  // Start time of the connection.
-  optional int64 start_time_millis = 1;
-
-  // Type of the connection.
-  optional ConnectionType connection_type = 2;
-
-  // WPS method.
-  optional WpsMethod wps_method = 3 [default = WPS_NA];
-
-  // Duration to connect.
-  optional int32 duration_taken_to_connect_millis = 4;
-
-  // Failures that happen at the connectivity layer.
-  optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
-}
-
-// GroupEvent tracking group information from GroupStarted to GroupRemoved.
-message GroupEvent {
-
-  enum GroupRole {
-
-    GROUP_OWNER = 0;
-
-    GROUP_CLIENT = 1;
-  }
-
-  // The ID of network in supplicant for this group.
-  optional int32 net_id = 1;
-
-  // Start time of the group.
-  optional int64 start_time_millis = 2;
-
-  // Channel frequency used for Group.
-  optional int32 channel_frequency = 3;
-
-  // Is group owner or group client.
-  optional GroupRole group_role = 5;
-
-  // Number of connected clients.
-  optional int32 num_connected_clients = 6;
-
-  // Cumulative number of connected clients.
-  optional int32 num_cumulative_clients = 7;
-
-  // The session duration.
-  optional int32 session_duration_millis = 8;
-
-  // The idle duration. A group without any client is in idle.
-  optional int32 idle_duration_millis = 9;
-}
-
-// Easy Connect (DPP)
-message WifiDppLog {
-  reserved 6;
-
-  // Number of Configurator-Initiator requests
-  optional int32 num_dpp_configurator_initiator_requests = 1;
-
-  // Number of Enrollee-Initiator requests
-  optional int32 num_dpp_enrollee_initiator_requests = 2;
-
-  // Easy Connect (DPP) Enrollee success
-  optional int32 num_dpp_enrollee_success = 3;
-
-  // Easy Connect (DPP) Configurator success code bucket
-  repeated DppConfiguratorSuccessStatusHistogramBucket dpp_configurator_success_code = 4;
-
-  // Easy Connect (DPP) failure code bucket
-  repeated DppFailureStatusHistogramBucket dpp_failure_code = 5;
-
-  // Easy Connect (DPP) operation time bucket
-  repeated HistogramBucketInt32 dpp_operation_time = 7;
-
-  // Histogram bucket for Wi-Fi DPP configurator success
-  message DppConfiguratorSuccessStatusHistogramBucket {
-    // status type defining the bucket
-    optional DppConfiguratorSuccessCode dpp_status_type = 1;
-
-    // number of samples in the bucket
-    optional int32 count = 2;
-  }
-
-  // Histogram bucket for Wi-Fi DPP failures
-  message DppFailureStatusHistogramBucket {
-    // status type defining the bucket
-    optional DppFailureCode dpp_status_type = 1;
-
-    // number of samples in the bucket
-    optional int32 count = 2;
-  }
-
-  enum DppConfiguratorSuccessCode {
-    // Unknown success code
-    EASY_CONNECT_EVENT_SUCCESS_UNKNOWN = 0;
-
-    // Easy Connect Configurator success event: Configuration sent to enrollee
-    EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 1;
-  }
-
-  enum DppFailureCode {
-    // Unknown failure
-    EASY_CONNECT_EVENT_FAILURE_UNKNOWN = 0;
-
-    // Easy Connect Failure event: Scanned QR code is either not a Easy Connect URI, or the Easy
-    // Connect URI has errors.
-    EASY_CONNECT_EVENT_FAILURE_INVALID_URI = 1;
-
-    // Easy Connect Failure event: Bootstrapping/Authentication initialization process failure.
-    EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = 2;
-
-    // Easy Connect Failure event: Both devices are implementing the same role and are
-    // incompatible.
-    EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE = 3;
-
-    // Easy Connect Failure event: Configuration process has failed due to malformed message.
-    EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = 4;
-
-    // Easy Connect Failure event: Easy Connect request while in another Easy Connect exchange.
-    EASY_CONNECT_EVENT_FAILURE_BUSY = 5;
-
-    // Easy Connect Failure event: No response from the peer.
-    EASY_CONNECT_EVENT_FAILURE_TIMEOUT = 6;
-
-    // Easy Connect Failure event: General protocol failure.
-    EASY_CONNECT_EVENT_FAILURE_GENERIC = 7;
-
-    // Easy Connect Failure event: Feature or option is not supported.
-    EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = 8;
-
-    // Easy Connect Failure event: Invalid network provided to Easy Connect configurator.
-    // Network must either be WPA3-Personal (SAE) or WPA2-Personal (PSK).
-    EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = 9;
-  }
-}
-
-// WifiConfigStore read/write metrics.
-message WifiConfigStoreIO {
-  // Histogram of config store read durations.
-  repeated DurationBucket read_durations = 1;
-
-  // Histogram of config store write durations.
-  repeated DurationBucket write_durations = 2;
-
-  // Total Number of instances of write/read duration in this duration bucket.
-  message DurationBucket {
-    // Bucket covers duration : [range_start_ms, range_end_ms)
-    // The (inclusive) lower bound of read/write duration represented by this bucket
-    optional int32 range_start_ms = 1;
-
-    // The (exclusive) upper bound of read/write duration represented by this bucket
-    optional int32 range_end_ms = 2;
-
-    // Number of read/write durations that fit into this bucket
-    optional int32 count = 3;
-  }
-}
-
-// Histogram bucket counting with int32. Range is [start, end)
-message HistogramBucketInt32 {
-  // lower range of the bucket (inclusive)
-  optional int32 start = 1;
-
-  // upper range of the bucket (exclusive)
-  optional int32 end = 2;
-
-  // number of samples in the bucket
-  optional int32 count = 3;
-}
-
-// Counts occurrences of a int32 key
-message Int32Count {
-  // the key
-  optional int32 key = 1;
-
-  // the count
-  optional int32 count = 2;
-}
-
-message LinkProbeStats {
-  enum LinkProbeFailureReason {
-    // unknown reason
-    LINK_PROBE_FAILURE_REASON_UNKNOWN = 0;
-
-    // Specified MCS rate when it is unsupported by the driver.
-    LINK_PROBE_FAILURE_REASON_MCS_UNSUPPORTED = 1;
-
-    // Driver reported that no ACK was received for the transmitted probe.
-    LINK_PROBE_FAILURE_REASON_NO_ACK = 2;
-
-    // Driver failed to report on the status of the transmitted probe within the timeout.
-    LINK_PROBE_FAILURE_REASON_TIMEOUT = 3;
-
-    // An existing link probe is in progress.
-    LINK_PROBE_FAILURE_REASON_ALREADY_STARTED = 4;
-  }
-
-  // Counts the number of failures for each failure reason.
-  message LinkProbeFailureReasonCount {
-    // The failure reason.
-    optional LinkProbeFailureReason failure_reason = 1;
-
-    // The number of occurrences for this failure reason.
-    optional int32 count = 2;
-  }
-
-  // Counts the number of link probes for each experiment.
-  message ExperimentProbeCounts {
-    // The experiment ID.
-    optional string experiment_id = 1;
-
-    // The number of link probes triggered for this experiment.
-    optional int32 probe_count = 2;
-  }
-
-  // Counts the occurrences of RSSI values when a link probe succeeds.
-  repeated Int32Count success_rssi_counts = 1;
-
-  // Counts the occurrences of RSSI values when a link probe fails.
-  repeated Int32Count failure_rssi_counts = 2;
-
-  // Counts the occurrences of Link Speed values when a link probe succeeds.
-  repeated Int32Count success_link_speed_counts = 3;
-
-  // Counts the occurrences of Link Speed values when a link probe fails.
-  repeated Int32Count failure_link_speed_counts = 4;
-
-  // Histogram for the number of seconds since the last TX success when a link probe succeeds.
-  repeated HistogramBucketInt32 success_seconds_since_last_tx_success_histogram = 5;
-
-  // Histogram for the number of seconds since the last TX success when a link probe fails.
-  repeated HistogramBucketInt32 failure_seconds_since_last_tx_success_histogram = 6;
-
-  // Histogram for the elapsed time of successful link probes, in ms.
-  repeated HistogramBucketInt32 success_elapsed_time_ms_histogram = 7;
-
-  // Counts the occurrences of error codes for failed link probes.
-  repeated LinkProbeFailureReasonCount failure_reason_counts = 8;
-
-  // Counts the number of link probes for each experiment.
-  repeated ExperimentProbeCounts experiment_probe_counts = 9;
-}
-
-// Stores the decisions that were made by a experiment when compared against another experiment
-message NetworkSelectionExperimentDecisions {
-  // the id of one experiment
-  optional int32 experiment1_id = 1;
-
-  // the id of the other experiment
-  optional int32 experiment2_id = 2;
-
-  // Counts occurrences of the number of network choices there were when experiment1 makes the
-  // same network selection as experiment2.
-  // The keys are the number of network choices, and the values are the number of occurrences of
-  // this number of network choices when exp1 and exp2 make the same network selection.
-  repeated Int32Count same_selection_num_choices_counter = 3;
-
-  // Counts occurrences of the number of network choices there were when experiment1 makes the
-  // same network selection as experiment2.
-  // The keys are the number of network choices, and the values are the number of occurrences of
-  // this number of network choices when exp1 and exp2 make different network selections.
-  // Note that it is possible for the network selection to be different even when there only exists
-  // a single network choice, since choosing not to connect to that network is a valid choice.
-  repeated Int32Count different_selection_num_choices_counter = 4;
-}
-
-// NetworkRequest API metrics.
-message WifiNetworkRequestApiLog {
-  // Number of requests via this API surface.
-  optional int32 num_request = 1;
-
-  // Histogram of requests via this API surface to number of networks matched in scan results.
-  repeated HistogramBucketInt32 network_match_size_histogram = 2;
-
-  // Number of successful network connection from this API.
-  optional int32 num_connect_success = 3;
-
-  // Number of requests via this API surface that bypassed user approval.
-  optional int32 num_user_approval_bypass = 4;
-
-  // Number of requests via this API surface that was rejected by the user.
-  optional int32 num_user_reject = 5;
-
-  // Number of unique apps using this API surface.
-  optional int32 num_apps = 6;
-}
-
-// NetworkSuggestion API metrics.
-message WifiNetworkSuggestionApiLog {
-  // Number of modifications to their suggestions by apps.
-  optional int32 num_modification = 1;
-
-  // Number of successful network connection from app suggestions.
-  optional int32 num_connect_success = 2;
-
-  // Number of network connection failures from app suggestions.
-  optional int32 num_connect_failure = 3;
-
-  // Histogram for size of the network lists provided by various apps on the device.
-  repeated HistogramBucketInt32 network_list_size_histogram = 4;
-}
-
-// WifiLock metrics
-message WifiLockStats {
-    // Amount of time wifi is actively in HIGH_PERF mode (ms)
-    // This means the lock takes effect and the device takes the actions required for this mode
-    optional int64 high_perf_active_time_ms = 1;
-
-    // Amount of time wifi is actively in LOW_LATENCY mode (ms)
-    // This means the lock takes effect and the device takes the actions required for this mode
-    optional int64 low_latency_active_time_ms = 2;
-
-    // Histogram of HIGH_PERF lock acquisition duration (seconds)
-    // Note that acquiring the lock does not necessarily mean that device is actively in that mode
-    repeated HistogramBucketInt32 high_perf_lock_acq_duration_sec_histogram = 3;
-
-    // Histogram of LOW_LATENCY lock acquisition duration (seconds)
-    // Note that acquiring the lock does not necessarily mean that device is actively in that mode
-    repeated HistogramBucketInt32 low_latency_lock_acq_duration_sec_histogram = 4;
-
-    // Histogram of HIGH_PERF active session duration (seconds)
-    // This means the lock takes effect and the device takes the actions required for this mode
-    repeated HistogramBucketInt32 high_perf_active_session_duration_sec_histogram = 5;
-
-    // Histogram of LOW_LATENCY active session duration (seconds)
-    // This means the lock takes effect and the device takes the actions required for this mode
-    repeated HistogramBucketInt32 low_latency_active_session_duration_sec_histogram = 6;
-}
-
-// Stats on number of times Wi-Fi is turned on/off though the WifiManager#setWifiEnabled API
-message WifiToggleStats {
-  // Number of time Wi-Fi is turned on by privileged apps
-  optional int32 num_toggle_on_privileged = 1;
-
-  // Number of time Wi-Fi is turned off by privileged apps
-  optional int32 num_toggle_off_privileged = 2;
-
-  // Number of time Wi-Fi is turned on by normal apps
-  optional int32 num_toggle_on_normal = 3;
-
-  // Number of time Wi-Fi is turned off by normal apps
-  optional int32 num_toggle_off_normal = 4;
-}
-
-// Information about the Passpoint provision metrics.
-message PasspointProvisionStats {
-  enum ProvisionFailureCode {
-    // provisioning failure for unknown reason.
-    OSU_FAILURE_UNKNOWN = 0;
-
-    // The reason code for Provisioning Failure due to connection failure to OSU AP.
-    OSU_FAILURE_AP_CONNECTION = 1;
-
-    // The reason code for invalid server URL address.
-    OSU_FAILURE_SERVER_URL_INVALID = 2;
-
-    // The reason code for provisioning failure due to connection failure to the server.
-    OSU_FAILURE_SERVER_CONNECTION = 3;
-
-    // The reason code for provisioning failure due to invalid server certificate.
-    OSU_FAILURE_SERVER_VALIDATION = 4;
-
-    // The reason code for provisioning failure due to invalid service provider.
-    OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION = 5;
-
-    // The reason code for provisioning failure when a provisioning flow is aborted.
-    OSU_FAILURE_PROVISIONING_ABORTED = 6;
-
-    // The reason code for provisioning failure when a provisioning flow is not possible.
-    OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
-
-    // The reason code for provisioning failure due to invalid web url format for an OSU web page.
-    OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8;
-
-    // The reason code for provisioning failure when a command received is not the expected command
-    // type.
-    OSU_FAILURE_UNEXPECTED_COMMAND_TYPE = 9;
-
-    // The reason code for provisioning failure when a SOAP message is not the expected message
-    // type.
-    OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE = 10;
-
-    // The reason code for provisioning failure when a SOAP message exchange fails.
-    OSU_FAILURE_SOAP_MESSAGE_EXCHANGE = 11;
-
-    // The reason code for provisioning failure when a redirect listener fails to start.
-    OSU_FAILURE_START_REDIRECT_LISTENER = 12;
-
-    // The reason code for provisioning failure when a redirect listener timed out to receive a HTTP
-    // redirect response.
-    OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER = 13;
-
-    // The reason code for provisioning failure when there is no OSU activity to listen to intent.
-    OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14;
-
-    // The reason code for provisioning failure when the status of a SOAP message is not the
-    // expected message status.
-    OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS = 15;
-
-    // The reason code for provisioning failure when there is no PPS MO.
-    OSU_FAILURE_NO_PPS_MO = 16;
-
-    // The reason code for provisioning failure when there is no AAAServerTrustRoot node in a PPS
-    // MO.
-    OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17;
-
-    // The reason code for provisioning failure when there is no TrustRoot node for remediation
-    // server in a PPS MO.
-    OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE = 18;
-
-    // The reason code for provisioning failure when there is no TrustRoot node for policy server in
-    // a PPS MO.
-    OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE = 19;
-
-    // The reason code for provisioning failure when failing to retrieve trust root certificates
-    // used for validating server certificate for AAA, Remediation and Policy server.
-    OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES = 20;
-
-    // The reason code for provisioning failure when there is no trust root certificate for AAA
-    // server.
-    OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21;
-
-    // The reason code for provisioning failure when a {@link PasspointConfiguration} is failed to
-    // install.
-    OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22;
-
-    // The reason code for provisioning failure when an {@link OsuProvider} is not found for
-    // provisioning.
-    OSU_FAILURE_OSU_PROVIDER_NOT_FOUND = 23;
-  }
-
-  // Number of passpoint provisioning success
-  optional int32 num_provision_success = 1;
-
-  // Count for passpoint provisioning failure
-  repeated ProvisionFailureCount provision_failure_count = 2;
-
-  // Number of occurrences of a specific passpoint provision failure code
-  message ProvisionFailureCount {
-    // Failure code
-    optional ProvisionFailureCode failure_code = 1;
-
-    // Number of failure for the failure_code.
-    optional int32 count = 2;
-  }
-}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3067beb..c865384 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -80,6 +80,7 @@
         ":vold_aidl",
         ":gsiservice_aidl",
         ":platform-compat-config",
+        ":tethering-servicescore-srcs",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
@@ -155,3 +156,11 @@
     name: "protolog.conf.json.gz",
     src: ":services.core.json.gz",
 }
+
+// TODO: this should be removed after tethering migration done.
+filegroup {
+    name: "servicescore-tethering-src",
+    srcs: [
+        "java/com/android/server/connectivity/MockableSystemProperties.java",
+    ],
+}
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index bfd4247..3bcb36f 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -18,10 +18,10 @@
 
 import android.Manifest.permission;
 import android.annotation.Nullable;
-import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -210,14 +210,9 @@
     }
 
     private boolean canAccessLocation(int uid, String packageName) {
-        final PackageManager pm = mContext.getPackageManager();
-        final AppOpsManager appOpsManager =
-                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        return isLocationModeEnabled()
-                && pm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName)
-                == PackageManager.PERMISSION_GRANTED
-                && appOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName)
-                == AppOpsManager.MODE_ALLOWED;
+        return isLocationModeEnabled() && PermissionChecker.checkPermissionForPreflight(mContext,
+                permission.ACCESS_COARSE_LOCATION, PermissionChecker.PID_UNKNOWN, uid, packageName)
+                == PermissionChecker.PERMISSION_GRANTED;
     }
 
     private boolean isLocationModeEnabled() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 11cfe12..5df4543 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7758,7 +7758,7 @@
             holder = getContentProviderExternalUnchecked(name, null, callingUid,
                     "*checkContentProviderUriPermission*", userId);
             if (holder != null) {
-                return holder.provider.checkUriPermission(null, uri, callingUid, modeFlags);
+                return holder.provider.checkUriPermission(null, null, uri, callingUid, modeFlags);
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Content provider dead retrieving " + uri, e);
@@ -7923,7 +7923,7 @@
             sCallerIdentity.set(new Identity(
                     token, Binder.getCallingPid(), Binder.getCallingUid()));
             try {
-                pfd = cph.provider.openFile(null, uri, "r", null, token);
+                pfd = cph.provider.openFile(null, null, uri, "r", null, token);
             } catch (FileNotFoundException e) {
                 // do nothing; pfd will be returned null
             } finally {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f7ac040..6010b1dc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -57,6 +57,13 @@
     private final @NonNull AudioService mAudioService;
     private final @NonNull Context mContext;
 
+    /** Forced device usage for communications sent to AudioSystem */
+    private int mForcedUseForComm;
+    /**
+     * Externally reported force device usage state returned by getters: always consistent
+     * with requests by setters */
+    private int mForcedUseForCommExt;
+
     // Manages all connected devices, only ever accessed on the message loop
     private final AudioDeviceInventory mDeviceInventory;
     // Manages notifications to BT service
@@ -64,34 +71,24 @@
 
 
     //-------------------------------------------------------------------
-    /**
-     * Lock to guard:
-     * - any changes to the message queue: enqueueing or removing any message
-     * - state of A2DP enabled
-     * - force use for communication + SCO changes
-     */
-    private final Object mDeviceBrokerLock = new Object();
-
-    @GuardedBy("mDeviceBrokerLock")
+    // we use a different lock than mDeviceStateLock so as not to create
+    // lock contention between enqueueing a message and handling them
+    private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+    @GuardedBy("sLastDeviceConnectionMsgTimeLock")
     private static long sLastDeviceConnectMsgTime = 0;
 
+    // General lock to be taken whenever the state of the audio devices is to be checked or changed
+    private final Object mDeviceStateLock = new Object();
 
-    /** Request to override default use of A2DP for media */
-    @GuardedBy("mDeviceBrokerLock")
+    // Request to override default use of A2DP for media.
+    @GuardedBy("mDeviceStateLock")
     private boolean mBluetoothA2dpEnabled;
 
-    /** Forced device usage for communications sent to AudioSystem */
-    @GuardedBy("mDeviceBrokerLock")
-    private int mForcedUseForComm;
-    /**
-     * Externally reported force device usage state returned by getters: always consistent
-     * with requests by setters */
-    @GuardedBy("mDeviceBrokerLock")
-    private int mForcedUseForCommExt;
-
+    // lock always taken when accessing AudioService.mSetModeDeathHandlers
+    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
+    /*package*/ final Object mSetModeLock = new Object();
 
     //-------------------------------------------------------------------
-    /** Normal constructor used by AudioService */
     /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
         mContext = context;
         mAudioService = service;
@@ -130,37 +127,38 @@
     // All post* methods are asynchronous
 
     /*package*/ void onSystemReady() {
-        mBtHelper.onSystemReady();
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                mBtHelper.onSystemReady();
+            }
+        }
     }
 
     /*package*/ void onAudioServerDied() {
         // Restore forced usage for communications and record
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             AudioSystem.setParameters(
                     "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
             onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
             onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
-
-            // restore devices
-            sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
         }
+        // restore devices
+        sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
     }
 
     /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
-        synchronized (mDeviceBrokerLock) {
-            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
-                    useCase, config, eventSource);
-        }
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                useCase, config, eventSource);
     }
 
     /*package*/ void toggleHdmiIfConnected_Async() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
     }
 
     /*package*/ void disconnectAllBluetoothProfiles() {
+        synchronized (mDeviceStateLock) {
             mBtHelper.disconnectAllBluetoothProfiles();
+        }
     }
 
     /**
@@ -170,11 +168,15 @@
      * @param intent
      */
     /*package*/ void receiveBtEvent(@NonNull Intent intent) {
-        mBtHelper.receiveBtEvent(intent);
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                mBtHelper.receiveBtEvent(intent);
+            }
+        }
     }
 
     /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             if (mBluetoothA2dpEnabled == on) {
                 return;
             }
@@ -194,7 +196,7 @@
      * @return true if speakerphone state changed
      */
     /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             final boolean wasOn = isSpeakerphoneOn();
             if (on) {
                 if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
@@ -212,7 +214,7 @@
     }
 
     /*package*/ boolean isSpeakerphoneOn() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
         }
     }
@@ -221,7 +223,9 @@
             @AudioService.ConnectionState int state, String address, String name,
             String caller) {
         //TODO move logging here just like in setBluetooth* methods
-        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        }
     }
 
     private static final class BtDeviceConnectionInfo {
@@ -255,24 +259,27 @@
         final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
                 suppressNoisyIntent, a2dpVolume);
 
-        synchronized (mDeviceBrokerLock) {
-            // when receiving a request to change the connection state of a device, this last
-            // request is the source of truth, so cancel all previous requests
-            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                    device);
-            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
-                    device);
-            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                    device);
-            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                    device);
+        // when receiving a request to change the connection state of a device, this last request
+        // is the source of truth, so cancel all previous requests
+        removeAllA2dpConnectionEvents(device);
 
-            sendLMsgNoDelay(
-                    state == BluetoothProfile.STATE_CONNECTED
-                            ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
-                            : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                    SENDMSG_QUEUE, info);
-        }
+        sendLMsgNoDelay(
+                state == BluetoothProfile.STATE_CONNECTED
+                        ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
+                        : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                SENDMSG_QUEUE, info);
+    }
+
+    /** remove all previously scheduled connection and disconnection events for the given device */
+    private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) {
+        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                device);
+        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
+                device);
+        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                device);
+        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                device);
     }
 
     private static final class HearingAidDeviceConnectionInfo {
@@ -298,31 +305,28 @@
             boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
         final HearingAidDeviceConnectionInfo info = new HearingAidDeviceConnectionInfo(
                 device, state, suppressNoisyIntent, musicDevice, eventSource);
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
-        }
+        sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
     }
 
     // never called by system components
     /*package*/ void setBluetoothScoOnByApp(boolean on) {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
         }
     }
 
     /*package*/ boolean isBluetoothScoOnForApp() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
         }
     }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        final boolean isBtScoOn = mBtHelper.isBluetoothScoOn();
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             if (on) {
                 // do not accept SCO ON if SCO audio is not connected
-                if (!isBtScoOn) {
+                if (!mBtHelper.isBluetoothScoOn()) {
                     mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
                     return;
                 }
@@ -342,55 +346,58 @@
     }
 
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        return mDeviceInventory.startWatchingRoutes(observer);
-
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.startWatchingRoutes(observer);
+        }
     }
 
     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
-        return mDeviceInventory.getCurAudioRoutes();
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.getCurAudioRoutes();
+        }
     }
 
     /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
-        return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        synchronized (mDeviceStateLock) {
+            return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        }
     }
 
     /*package*/ boolean isBluetoothA2dpOn() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
-        }
+        sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
     }
 
     /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
-        synchronized (mDeviceBrokerLock) {
-            sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
-        }
+        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
     /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
-        }
+        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    }
+
+    @GuardedBy("mSetModeLock")
+    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        synchronized (mDeviceStateLock) {
+            mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
         }
     }
 
-    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
-                @NonNull String eventSource) {
-        mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
-    }
-
+    @GuardedBy("mSetModeLock")
     /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
-        mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+        synchronized (mDeviceStateLock) {
+            mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+        }
     }
 
     //---------------------------------------------------------------------
@@ -453,109 +460,77 @@
     //---------------------------------------------------------------------
     // Message handling on behalf of helper classes
     /*package*/ void postBroadcastScoConnectionState(int state) {
-        synchronized (mDeviceBrokerLock) {
-            sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
-        }
+        sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
     }
 
     /*package*/ void postBroadcastBecomingNoisy() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
-        }
+        sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
     }
 
     /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
-                            ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
-                            : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                    SENDMSG_QUEUE,
-                    state, btDeviceInfo, delay);
-        }
+        sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
+                        ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
+                        : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
     }
 
     /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
-                    state, btDeviceInfo, delay);
-        }
+        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
     }
 
     /*package*/ void postSetWiredDeviceConnectionState(
             AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE,
-                    connectionState, delay);
-        }
+        sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
     }
 
     /*package*/ void postSetHearingAidConnectionState(
             @AudioService.BtProfileConnectionState int state,
             @NonNull BluetoothDevice device, int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
-                    state,
-                    device,
-                    delay);
-        }
+        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+                state,
+                device,
+                delay);
     }
 
     /*package*/ void postDisconnectA2dp() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectA2dpSink() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectHearingAid() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
     }
 
     /*package*/ void postDisconnectHeadset() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
-        }
+        sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
     }
 
     /*package*/ void postBtA2dpProfileConnected(BluetoothA2dp a2dpProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
     }
 
     /*package*/ void postBtA2dpSinkProfileConnected(BluetoothProfile profile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
     }
 
     /*package*/ void postBtHeasetProfileConnected(BluetoothHeadset headsetProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE,
-                    headsetProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE, headsetProfile);
     }
 
     /*package*/ void postBtHearingAidProfileConnected(BluetoothHearingAid hearingAidProfile) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
-                    hearingAidProfile);
-        }
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
+                hearingAidProfile);
     }
 
     /*package*/ void postScoClientDied(Object obj) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
-        }
+        sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
     //---------------------------------------------------------------------
@@ -570,7 +545,7 @@
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).append(" src:").append(source).toString();
 
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             mBluetoothA2dpEnabled = on;
             mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
             onSetForceUse(
@@ -582,85 +557,71 @@
 
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
                                                        String deviceName) {
-        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        }
     }
 
     /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
         final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-        synchronized (mDeviceBrokerLock) {
-            sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
-                    btDeviceInfo);
-        }
+        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+                btDeviceInfo);
     }
 
     /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
-        synchronized (mDeviceBrokerLock) {
-            sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
-        }
+        sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
     }
 
     /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
-        synchronized (mDeviceBrokerLock) {
-            mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
-        }
+        mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
     }
 
     /*package*/ void postReportNewRoutes() {
-        synchronized (mDeviceBrokerLock) {
-            sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
-        }
+        sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
     }
 
     /*package*/ void cancelA2dpDockTimeout() {
-        synchronized (mDeviceBrokerLock) {
-            mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-        }
+        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
     }
 
-    // FIXME: used by?
     /*package*/ void postA2dpActiveDeviceChange(
                     @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        synchronized (mDeviceBrokerLock) {
-            sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
-        }
+        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
     }
 
     /*package*/ boolean hasScheduledA2dpDockTimeout() {
-        synchronized (mDeviceBrokerLock) {
-            return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-        }
+        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
     }
 
     // must be called synchronized on mConnectedDevices
     /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
-        synchronized (mDeviceBrokerLock) {
-            return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                    new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
-                    || mBrokerHandler.hasMessages(
-                            MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
-        }
+        return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
+                || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
     }
 
     /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
-        synchronized (mDeviceBrokerLock) {
-            sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
-        }
+        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
     }
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
-        mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        synchronized (mDeviceStateLock) {
+            mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        }
     }
 
     /*package*/ boolean getBluetoothA2dpEnabled() {
-        synchronized (mDeviceBrokerLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
-        return mBtHelper.getA2dpCodec(device);
+        synchronized (mDeviceStateLock) {
+            return mBtHelper.getA2dpCodec(device);
+        }
     }
 
     /*package*/ void dump(PrintWriter pw, String prefix) {
@@ -748,101 +709,156 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_RESTORE_DEVICES:
-                    mDeviceInventory.onRestoreDevices();
-                    mBtHelper.onAudioServerDiedRestoreA2dp();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onRestoreDevices();
+                        mBtHelper.onAudioServerDiedRestoreA2dp();
+                    }
                     break;
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    mDeviceInventory.onSetWiredDeviceConnectionState(
-                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetWiredDeviceConnectionState(
+                                (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    }
                     break;
                 case MSG_I_BROADCAST_BT_CONNECTION_STATE:
-                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    }
                     break;
                 case MSG_IIL_SET_FORCE_USE: // intended fall-through
                 case MSG_IIL_SET_FORCE_BT_A2DP_USE:
                     onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
                     break;
                 case MSG_REPORT_NEW_ROUTES:
-                    mDeviceInventory.onReportNewRoutes();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onReportNewRoutes();
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-                    mDeviceInventory.onSetA2dpSinkConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSinkConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                    mDeviceInventory.onSetA2dpSourceConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSourceConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                    mDeviceInventory.onSetHearingAidConnectionState(
-                            (BluetoothDevice) msg.obj, msg.arg1,
-                            mAudioService.getHearingAidStreamType());
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetHearingAidConnectionState(
+                                (BluetoothDevice) msg.obj, msg.arg1,
+                                mAudioService.getHearingAidStreamType());
+                    }
                     break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
-                    mBtHelper.resetBluetoothSco();
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.resetBluetoothSco();
+                        }
+                    }
                     break;
                 case MSG_IL_BTA2DP_DOCK_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
-                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
                     final int a2dpCodec;
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
-                    // FIXME why isn't the codec coming with the request? codec should be
-                    //       provided by BT when it calls
-                    //       AudioManager.handleBluetoothA2dpDeviceConfigChange(BluetoothDevice)
-                    a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
-                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
-                                    BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                    synchronized (mDeviceStateLock) {
+                        a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                                new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
+                                        BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                    }
                     break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
                     onSendBecomingNoisyIntent();
                     break;
                 case MSG_II_SET_HEARING_AID_VOLUME:
-                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    }
                     break;
                 case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
-                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    }
                     break;
                 case MSG_I_DISCONNECT_BT_SCO:
-                    mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.disconnectBluetoothSco(msg.arg1);
+                        }
+                    }
                     break;
                 case MSG_L_SCOCLIENT_DIED:
-                    mBtHelper.scoClientDied(msg.obj);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.scoClientDied(msg.obj);
+                        }
+                    }
                     break;
                 case MSG_TOGGLE_HDMI:
-                    mDeviceInventory.onToggleHdmi();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onToggleHdmi();
+                    }
                     break;
                 case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
-                            BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
+                                 BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
+                    }
                     break;
                 case MSG_DISCONNECT_A2DP:
-                    mDeviceInventory.disconnectA2dp();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectA2dp();
+                    }
                     break;
                 case MSG_DISCONNECT_A2DP_SINK:
-                    mDeviceInventory.disconnectA2dpSink();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectA2dpSink();
+                    }
                     break;
                 case MSG_DISCONNECT_BT_HEARING_AID:
-                    mDeviceInventory.disconnectHearingAid();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.disconnectHearingAid();
+                    }
                     break;
                 case MSG_DISCONNECT_BT_HEADSET:
-                    mBtHelper.disconnectHeadset();
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.disconnectHeadset();
+                        }
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
-                    mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK:
-                    mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID:
-                    mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
+                    }
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
-                    mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                        }
+                    }
                     break;
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
@@ -855,9 +871,11 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy
                                     + " vol=" + info.mVolume)).printLog(TAG));
-                    mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
-                            info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
-                            AudioSystem.DEVICE_NONE, info.mVolume);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                                info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
+                                AudioSystem.DEVICE_NONE, info.mVolume);
+                    }
                 } break;
                 case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: {
                     final HearingAidDeviceConnectionInfo info =
@@ -867,8 +885,10 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " supprNoisy=" + info.mSupprNoisy
                                     + " src=" + info.mEventSource)).printLog(TAG));
-                    mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
-                            info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                                info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
+                    }
                 } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
@@ -953,57 +973,46 @@
     /** If the msg is already queued, queue this one and leave the old. */
     private static final int SENDMSG_QUEUE = 2;
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendMsg(int msg, int existingMsgPolicy, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
     }
 
-    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
                             int delay) {
         if (existingMsgPolicy == SENDMSG_REPLACE) {
@@ -1022,29 +1031,31 @@
             Binder.restoreCallingIdentity(identity);
         }
 
-        long time = SystemClock.uptimeMillis() + delay;
+        synchronized (sLastDeviceConnectionMsgTimeLock) {
+            long time = SystemClock.uptimeMillis() + delay;
 
-        switch (msg) {
-            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
-            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                if (sLastDeviceConnectMsgTime >= time) {
-                    // add a little delay to make sure messages are ordered as expected
-                    time = sLastDeviceConnectMsgTime + 30;
-                }
-                sLastDeviceConnectMsgTime = time;
-                break;
-            default:
-                break;
+            switch (msg) {
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    if (sLastDeviceConnectMsgTime >= time) {
+                        // add a little delay to make sure messages are ordered as expected
+                        time = sLastDeviceConnectMsgTime + 30;
+                    }
+                    sLastDeviceConnectMsgTime = time;
+                    break;
+                default:
+                    break;
+            }
+
+            mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+                    time);
         }
-
-        mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
-                time);
     }
 
     //-------------------------------------------------------------
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 3933fb2..90973a8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -159,6 +159,7 @@
     }
 
     // only public for mocking/spying
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @VisibleForTesting
     public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
             @AudioService.BtProfileConnectionState int state) {
@@ -283,6 +284,7 @@
         }
     }
 
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ void onBluetoothA2dpActiveDeviceChange(
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
         final BluetoothDevice btDevice = btInfo.getBtDevice();
@@ -555,6 +557,7 @@
     }
 
     // only public for mocking/spying
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @VisibleForTesting
     public void setBluetoothA2dpDeviceConnectionState(
             @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cc50e37..0d493b8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -470,11 +470,12 @@
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
-    private final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+    // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+    //TODO candidate to be moved to separate class that handles synchronization
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
             new ArrayList<SetModeDeathHandler>();
 
-    private volatile int mCurrentModeOwnerPid = 0;
-
     // true if boot sequence has been completed
     private boolean mSystemReady;
     // true if Intent.ACTION_USER_SWITCHED has ever been received
@@ -3191,10 +3192,15 @@
      * @return 0 if nobody owns the mode
      */
     /*package*/ int getModeOwnerPid() {
-        return  mCurrentModeOwnerPid;
+        int modeOwnerPid = 0;
+        try {
+            modeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+        } catch (Exception e) {
+            // nothing to do, modeOwnerPid is not modified
+        }
+        return modeOwnerPid;
     }
 
-
     private class SetModeDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mPid;
@@ -3208,7 +3214,7 @@
         public void binderDied() {
             int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
-            synchronized (mSetModeDeathHandlers) {
+            synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -3219,15 +3225,11 @@
                 } else {
                     newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
                 }
-
-                if (newModeOwnerPid != oldModeOwnerPid) {
-                    mCurrentModeOwnerPid = newModeOwnerPid;
-                    // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
-                    // connections not started by the application changing the mode when pid changes
-                    if (newModeOwnerPid != 0) {
-                        mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-                    }
-                }
+            }
+            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+            // SCO connections not started by the application changing the mode when pid changes
+            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
             }
         }
 
@@ -3250,17 +3252,15 @@
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
-        if (DEBUG_MODE) {
-            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
-        }
+        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
 
-        if ((mode == AudioSystem.MODE_IN_CALL)
-                && (mContext.checkCallingOrSelfPermission(
+        if ( (mode == AudioSystem.MODE_IN_CALL) &&
+                (mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_PHONE_STATE)
-                        != PackageManager.PERMISSION_GRANTED)) {
+                            != PackageManager.PERMISSION_GRANTED)) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
             return;
@@ -3272,7 +3272,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized (mSetModeDeathHandlers) {
+        synchronized (mDeviceBroker.mSetModeLock) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3280,21 +3280,17 @@
                 mode = mMode;
             }
             newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
-
-            if (newModeOwnerPid != oldModeOwnerPid) {
-                mCurrentModeOwnerPid = newModeOwnerPid;
-                // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-                // SCO connections not started by the application changing the mode when pid changes
-                if (newModeOwnerPid != 0) {
-                    mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-                }
-            }
+        }
+        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+        // SCO connections not started by the application changing the mode when pid changes
+        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
         }
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
-    @GuardedBy("mSetModeDeathHandlers")
+    @GuardedBy("mDeviceBroker.mSetModeLock")
     private int setModeInt(int mode, IBinder cb, int pid, String caller) {
         if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
                 + caller + ")"); }
@@ -3633,7 +3629,9 @@
                 !mSystemReady) {
             return;
         }
-        mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
+        synchronized (mDeviceBroker.mSetModeLock) {
+            mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
+        }
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -3645,7 +3643,9 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-        mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
+        synchronized (mDeviceBroker.mSetModeLock) {
+            mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
+        }
     }
 
 
@@ -4406,7 +4406,7 @@
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
-    //  2   mSetModeDeathHandlers
+    //  2   mSetModeLock
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
     private class VolumeStreamState {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 625b6b6..9f1a6bd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -171,6 +171,8 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
@@ -243,6 +245,8 @@
         return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void receiveBtEvent(Intent intent) {
         final String action = intent.getAction();
         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
@@ -329,6 +333,8 @@
      *
      * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
      */
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
         checkScoAudioState();
         if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
@@ -337,6 +343,8 @@
         clearAllScoClients(exceptPid, true);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, true);
@@ -356,6 +364,8 @@
         Binder.restoreCallingIdentity(ident);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
             @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, false);
@@ -413,6 +423,8 @@
         mDeviceBroker.postDisconnectHearingAid();
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void resetBluetoothSco() {
         clearAllScoClients(0, false);
         mScoAudioState = SCO_STATE_INACTIVE;
@@ -421,6 +433,8 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectHeadset() {
         setBtScoActiveDevice(null);
         mBluetoothHeadset = null;
@@ -466,6 +480,8 @@
                 /*eventSource*/ "mBluetoothProfileServiceListener");
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -552,6 +568,8 @@
         return result;
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void setBtScoActiveDevice(BluetoothDevice btDevice) {
         Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
@@ -634,6 +652,8 @@
             };
 
     //----------------------------------------------------------------------
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void scoClientDied(Object obj) {
         final ScoClient client = (ScoClient) obj;
         Log.w(TAG, "SCO client died");
@@ -664,6 +684,8 @@
             mDeviceBroker.postScoClientDied(this);
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void incCount(int scoAudioMode) {
             if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) {
@@ -683,6 +705,8 @@
             mStartcount++;
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void decCount() {
             if (mStartcount == 0) {
@@ -702,6 +726,8 @@
             }
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void clearCount(boolean stopSco) {
             if (mStartcount != 0) {
@@ -738,6 +764,8 @@
             return count;
         }
 
+        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+        //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         private boolean requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
@@ -931,6 +959,8 @@
         return null;
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void clearAllScoClients(int exceptPid, boolean stopSco) {
         ScoClient savedClient = null;
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index d5706a5..3b0069c 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -111,6 +111,8 @@
     @GuardedBy("mDaltonizerModeLock")
     private int mDaltonizerMode = -1;
 
+    private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER);
+
     /* package */ DisplayTransformManager() {
     }
 
@@ -195,25 +197,22 @@
      * Propagates the provided color transformation matrix to the SurfaceFlinger.
      */
     private static void applyColorMatrix(float[] m) {
-        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
-        if (flinger != null) {
-            final Parcel data = Parcel.obtain();
-            data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            if (m != null) {
-                data.writeInt(1);
-                for (int i = 0; i < 16; i++) {
-                    data.writeFloat(m[i]);
-                }
-            } else {
-                data.writeInt(0);
+        final Parcel data = Parcel.obtain();
+        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+        if (m != null) {
+            data.writeInt(1);
+            for (int i = 0; i < 16; i++) {
+                data.writeFloat(m[i]);
             }
-            try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to set color transform", ex);
-            } finally {
-                data.recycle();
-            }
+        } else {
+            data.writeInt(0);
+        }
+        try {
+            sFlinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to set color transform", ex);
+        } finally {
+            data.recycle();
         }
     }
 
@@ -221,18 +220,15 @@
      * Propagates the provided Daltonization mode to the SurfaceFlinger.
      */
     private static void applyDaltonizerMode(int mode) {
-        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
-        if (flinger != null) {
-            final Parcel data = Parcel.obtain();
-            data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            data.writeInt(mode);
-            try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to set Daltonizer mode", ex);
-            } finally {
-                data.recycle();
-            }
+        final Parcel data = Parcel.obtain();
+        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+        data.writeInt(mode);
+        try {
+            sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to set Daltonizer mode", ex);
+        } finally {
+            data.recycle();
         }
     }
 
@@ -286,20 +282,17 @@
      * #SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED}.
      */
     public boolean isDeviceColorManaged() {
-        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
-        if (flinger != null) {
-            final Parcel data = Parcel.obtain();
-            final Parcel reply = Parcel.obtain();
-            data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0);
-                return reply.readBoolean();
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to query wide color support", ex);
-            } finally {
-                data.recycle();
-                reply.recycle();
-            }
+        final Parcel data = Parcel.obtain();
+        final Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+        try {
+            sFlinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0);
+            return reply.readBoolean();
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to query wide color support", ex);
+        } finally {
+            data.recycle();
+            reply.recycle();
         }
         return false;
     }
@@ -309,18 +302,15 @@
      */
     private void applySaturation(float saturation) {
         SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
-        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
-        if (flinger != null) {
-            final Parcel data = Parcel.obtain();
-            data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            data.writeFloat(saturation);
-            try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to set saturation", ex);
-            } finally {
-                data.recycle();
-            }
+        final Parcel data = Parcel.obtain();
+        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+        data.writeFloat(saturation);
+        try {
+            sFlinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to set saturation", ex);
+        } finally {
+            data.recycle();
         }
     }
 
@@ -334,21 +324,18 @@
                 Integer.toString(compositionColorMode));
         }
 
-        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
-        if (flinger != null) {
-            final Parcel data = Parcel.obtain();
-            data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            data.writeInt(color);
-            if (compositionColorMode != Display.COLOR_MODE_INVALID) {
-                data.writeInt(compositionColorMode);
-            }
-            try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0);
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to set display color", ex);
-            } finally {
-                data.recycle();
-            }
+        final Parcel data = Parcel.obtain();
+        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+        data.writeInt(color);
+        if (compositionColorMode != Display.COLOR_MODE_INVALID) {
+            data.writeInt(compositionColorMode);
+        }
+        try {
+            sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to set display color", ex);
+        } finally {
+            data.recycle();
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
new file mode 100644
index 0000000..99b1ef4
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -0,0 +1,300 @@
+/*
+ * 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.server.notification;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Provides an interface to write and query for notification history data for a user from a Protocol
+ * Buffer database.
+ *
+ * Periodically writes the buffered history to disk but can also accept force writes based on
+ * outside changes (like a pending shutdown).
+ */
+public class NotificationHistoryDatabase {
+    private static final int DEFAULT_CURRENT_VERSION = 1;
+
+    private static final String TAG = "NotiHistoryDatabase";
+    private static final boolean DEBUG = NotificationManagerService.DBG;
+    private static final int HISTORY_RETENTION_DAYS = 2;
+    private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20;
+
+    private final Object mLock = new Object();
+    private Handler mFileWriteHandler;
+    @VisibleForTesting
+    // List of files holding history information, sorted newest to oldest
+    final LinkedList<AtomicFile> mHistoryFiles;
+    private final GregorianCalendar mCal;
+    private final File mHistoryDir;
+    private final File mVersionFile;
+    // Current version of the database files schema
+    private int mCurrentVersion;
+    private final WriteBufferRunnable mWriteBufferRunnable;
+
+    // Object containing posted notifications that have not yet been written to disk
+    @VisibleForTesting
+    NotificationHistory mBuffer;
+
+    public NotificationHistoryDatabase(File dir) {
+        mCurrentVersion = DEFAULT_CURRENT_VERSION;
+        mVersionFile = new File(dir, "version");
+        mHistoryDir = new File(dir, "history");
+        mHistoryFiles = new LinkedList<>();
+        mCal = new GregorianCalendar();
+        mBuffer = new NotificationHistory();
+        mWriteBufferRunnable = new WriteBufferRunnable();
+    }
+
+    public void init(Handler fileWriteHandler) {
+        synchronized (mLock) {
+            mFileWriteHandler = fileWriteHandler;
+
+            try {
+                mHistoryDir.mkdir();
+                mVersionFile.createNewFile();
+            } catch (Exception e) {
+                Slog.e(TAG, "could not create needed files", e);
+            }
+
+            checkVersionAndBuildLocked();
+            indexFilesLocked();
+            prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
+        }
+    }
+
+    private void indexFilesLocked() {
+        mHistoryFiles.clear();
+        final File[] files = mHistoryDir.listFiles();
+        if (files == null) {
+            return;
+        }
+
+        // Sort with newest files first
+        Arrays.sort(files, (lhs, rhs) -> Long.compare(rhs.lastModified(), lhs.lastModified()));
+
+        for (File file : files) {
+            mHistoryFiles.addLast(new AtomicFile(file));
+        }
+    }
+
+    private void checkVersionAndBuildLocked() {
+        int version;
+        try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) {
+            version = Integer.parseInt(reader.readLine());
+        } catch (NumberFormatException | IOException e) {
+            version = 0;
+        }
+
+        if (version != mCurrentVersion && mVersionFile.exists()) {
+            try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
+                writer.write(Integer.toString(mCurrentVersion));
+                writer.write("\n");
+                writer.flush();
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to write new version");
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    void forceWriteToDisk() {
+        if (!mFileWriteHandler.hasCallbacks(mWriteBufferRunnable)) {
+            mFileWriteHandler.post(mWriteBufferRunnable);
+        }
+    }
+
+    void onPackageRemoved(String packageName) {
+        RemovePackageRunnable rpr = new RemovePackageRunnable(packageName);
+        mFileWriteHandler.post(rpr);
+    }
+
+    public void addNotification(final HistoricalNotification notification) {
+        synchronized (mLock) {
+            mBuffer.addNotificationToWrite(notification);
+            // Each time we have new history to write to disk, schedule a write in [interval] ms
+            if (mBuffer.getHistoryCount() == 1) {
+                mFileWriteHandler.postDelayed(mWriteBufferRunnable, WRITE_BUFFER_INTERVAL_MS);
+            }
+        }
+    }
+
+    public NotificationHistory readNotificationHistory() {
+        synchronized (mLock) {
+            NotificationHistory notifications = new NotificationHistory();
+
+            for (AtomicFile file : mHistoryFiles) {
+                try {
+                    readLocked(
+                            file, notifications, new NotificationHistoryFilter.Builder().build());
+                } catch (Exception e) {
+                    Slog.e(TAG, "error reading " + file.getBaseFile().getName(), e);
+                }
+            }
+
+            return notifications;
+        }
+    }
+
+    public NotificationHistory readNotificationHistory(String packageName, String channelId,
+            int maxNotifications) {
+        synchronized (mLock) {
+            NotificationHistory notifications = new NotificationHistory();
+
+            for (AtomicFile file : mHistoryFiles) {
+                try {
+                    readLocked(file, notifications,
+                            new NotificationHistoryFilter.Builder()
+                                    .setPackage(packageName)
+                                    .setChannel(packageName, channelId)
+                                    .setMaxNotifications(maxNotifications)
+                                    .build());
+                    if (maxNotifications == notifications.getHistoryCount()) {
+                        // No need to read any more files
+                        break;
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "error reading " + file.getBaseFile().getName(), e);
+                }
+            }
+
+            return notifications;
+        }
+    }
+
+    /**
+     * Remove any files that are too old.
+     */
+    public void prune(final int retentionDays, final long currentTimeMillis) {
+        synchronized (mLock) {
+            mCal.setTimeInMillis(currentTimeMillis);
+            mCal.add(Calendar.DATE, -1 * retentionDays);
+
+            while (!mHistoryFiles.isEmpty()) {
+                final AtomicFile currentOldestFile = mHistoryFiles.getLast();
+                final long age = currentTimeMillis
+                        - currentOldestFile.getBaseFile().lastModified();
+                if (age > mCal.getTimeInMillis()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName());
+                    }
+                    currentOldestFile.delete();
+                    mHistoryFiles.removeLast();
+                } else {
+                    // all remaining files are newer than the cut off
+                    return;
+                }
+            }
+        }
+    }
+
+    private void writeLocked(AtomicFile file, NotificationHistory notifications)
+            throws IOException {
+        FileOutputStream fos = file.startWrite();
+        try {
+            NotificationHistoryProtoHelper.write(fos, notifications, mCurrentVersion);
+            file.finishWrite(fos);
+            fos = null;
+        } finally {
+            // When fos is null (successful write), this will no-op
+            file.failWrite(fos);
+        }
+    }
+
+    private static void readLocked(AtomicFile file, NotificationHistory notificationsOut,
+            NotificationHistoryFilter filter) throws IOException {
+        try (FileInputStream in = file.openRead()) {
+            NotificationHistoryProtoHelper.read(in, notificationsOut, filter);
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "Cannot file " + file.getBaseFile().getName(), e);
+            throw e;
+        }
+    }
+
+    private final class WriteBufferRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "WriteBufferRunnable");
+            synchronized (mLock) {
+                final AtomicFile latestNotificationsFiles = new AtomicFile(
+                        new File(mHistoryDir, String.valueOf(System.currentTimeMillis())));
+                try {
+                    writeLocked(latestNotificationsFiles, mBuffer);
+                    mHistoryFiles.addFirst(latestNotificationsFiles);
+                    mBuffer = new NotificationHistory();
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e);
+                }
+            }
+        }
+    }
+
+    private final class RemovePackageRunnable implements Runnable {
+        private String mPkg;
+
+        public RemovePackageRunnable(String pkg) {
+            mPkg = pkg;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "RemovePackageRunnable");
+            synchronized (mLock) {
+                // Remove packageName entries from pending history
+                mBuffer.removeNotificationsFromWrite(mPkg);
+
+                // Remove packageName entries from files on disk, and rewrite them to disk
+                // Since we sort by modified date, we have to update the files oldest to newest to
+                // maintain the original ordering
+                Iterator<AtomicFile> historyFileItr = mHistoryFiles.descendingIterator();
+                while (historyFileItr.hasNext()) {
+                    final AtomicFile af = historyFileItr.next();
+                    try {
+                        final NotificationHistory notifications = new NotificationHistory();
+                        readLocked(af, notifications,
+                                new NotificationHistoryFilter.Builder().build());
+                        notifications.removeNotificationsFromWrite(mPkg);
+                        writeLocked(af, notifications);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Cannot clean up file on pkg removal "
+                                + af.getBaseFile().getName(), e);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryFilter.java b/services/core/java/com/android/server/notification/NotificationHistoryFilter.java
new file mode 100644
index 0000000..c3b2e73
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationHistoryFilter.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.notification;
+
+import android.annotation.NonNull;
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+public final class NotificationHistoryFilter {
+    private String mPackage;
+    private String mChannel;
+    private int mNotificationCount;
+
+    private NotificationHistoryFilter() {}
+
+    public String getPackage() {
+        return mPackage;
+    }
+
+    public String getChannel() {
+        return mChannel;
+    }
+
+    public int getMaxNotifications() {
+        return mNotificationCount;
+    }
+
+    /**
+     * Returns whether any of the filtering conditions are set
+     */
+    public boolean isFiltering() {
+        return getPackage() != null || getChannel() != null
+                || mNotificationCount < Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns true if this notification passes the package and channel name filter, false
+     * otherwise.
+     */
+    public boolean matchesPackageAndChannelFilter(HistoricalNotification notification) {
+        if (!TextUtils.isEmpty(getPackage())) {
+            if (!getPackage().equals(notification.getPackage())) {
+                return false;
+            } else {
+                if (!TextUtils.isEmpty(getChannel())
+                        && !getChannel().equals(notification.getChannelId())) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the NotificationHistory can accept another notification.
+     */
+    public boolean matchesCountFilter(NotificationHistory notifications) {
+        return notifications.getHistoryCount() < mNotificationCount;
+    }
+
+    public static final class Builder {
+        private String mPackage = null;
+        private String mChannel = null;
+        private int mNotificationCount = Integer.MAX_VALUE;
+
+        /**
+         * Constructor
+         */
+        public Builder() {}
+
+        /**
+         * Sets a package name filter
+         */
+        public Builder setPackage(String aPackage) {
+            mPackage = aPackage;
+            return this;
+        }
+
+        /**
+         * Sets a channel name filter. Only valid if there is also a package name filter
+         */
+        public Builder setChannel(String pkg, String channel) {
+            setPackage(pkg);
+            mChannel = channel;
+            return this;
+        }
+
+        /**
+         * Sets the max historical notifications
+         */
+        public Builder setMaxNotifications(int notificationCount) {
+            mNotificationCount = notificationCount;
+            return this;
+        }
+
+        /**
+         * Makes a NotificationHistoryFilter
+         */
+        public NotificationHistoryFilter build() {
+            NotificationHistoryFilter filter = new NotificationHistoryFilter();
+            filter.mPackage = mPackage;
+            filter.mChannel = mChannel;
+            filter.mNotificationCount = mNotificationCount;
+            return filter;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
new file mode 100644
index 0000000..2831d37
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
@@ -0,0 +1,321 @@
+/*
+ * 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.server.notification;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.notification.NotificationHistoryProto.Notification;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Notification history reader/writer for Protocol Buffer format
+ */
+final class NotificationHistoryProtoHelper {
+    private static final String TAG = "NotifHistoryProto";
+
+    // Static-only utility class.
+    private NotificationHistoryProtoHelper() {}
+
+    private static List<String> readStringPool(ProtoInputStream proto) throws IOException {
+        final long token = proto.start(NotificationHistoryProto.STRING_POOL);
+        List<String> stringPool;
+        if (proto.nextField(NotificationHistoryProto.StringPool.SIZE)) {
+            stringPool = new ArrayList(proto.readInt(NotificationHistoryProto.StringPool.SIZE));
+        } else {
+            stringPool = new ArrayList();
+        }
+        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (proto.getFieldNumber()) {
+                case (int) NotificationHistoryProto.StringPool.STRINGS:
+                    stringPool.add(proto.readString(NotificationHistoryProto.StringPool.STRINGS));
+                    break;
+            }
+        }
+        proto.end(token);
+        return stringPool;
+    }
+
+    private static void writeStringPool(ProtoOutputStream proto,
+            final NotificationHistory notifications) {
+        final long token = proto.start(NotificationHistoryProto.STRING_POOL);
+        final String[] pooledStrings = notifications.getPooledStringsToWrite();
+        proto.write(NotificationHistoryProto.StringPool.SIZE, pooledStrings.length);
+        for (int i = 0; i < pooledStrings.length; i++) {
+            proto.write(NotificationHistoryProto.StringPool.STRINGS, pooledStrings[i]);
+        }
+        proto.end(token);
+    }
+
+    private static void readNotification(ProtoInputStream proto, List<String> stringPool,
+            NotificationHistory notifications, NotificationHistoryFilter filter)
+            throws IOException {
+        final long token = proto.start(NotificationHistoryProto.NOTIFICATION);
+        try {
+            HistoricalNotification notification = readNotification(proto, stringPool);
+            if (filter.matchesPackageAndChannelFilter(notification)
+                    && filter.matchesCountFilter(notifications)) {
+                notifications.addNotificationToWrite(notification);
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error reading notification", e);
+        } finally {
+            proto.end(token);
+        }
+    }
+
+    private static HistoricalNotification readNotification(ProtoInputStream parser,
+            List<String> stringPool) throws IOException {
+        final HistoricalNotification.Builder notification = new HistoricalNotification.Builder();
+        String pkg = null;
+        while (true) {
+            switch (parser.nextField()) {
+                case (int) NotificationHistoryProto.Notification.PACKAGE:
+                    pkg = parser.readString(Notification.PACKAGE);
+                    notification.setPackage(pkg);
+                    stringPool.add(pkg);
+                    break;
+                case (int) Notification.PACKAGE_INDEX:
+                    pkg = stringPool.get(parser.readInt(Notification.PACKAGE_INDEX) - 1);
+                    notification.setPackage(pkg);
+                    break;
+                case (int) Notification.CHANNEL_NAME:
+                    String channelName = parser.readString(Notification.CHANNEL_NAME);
+                    notification.setChannelName(channelName);
+                    stringPool.add(channelName);
+                    break;
+                case (int) Notification.CHANNEL_NAME_INDEX:
+                    notification.setChannelName(stringPool.get(parser.readInt(
+                            Notification.CHANNEL_NAME_INDEX) - 1));
+                    break;
+                case (int) Notification.CHANNEL_ID:
+                    String channelId = parser.readString(Notification.CHANNEL_ID);
+                    notification.setChannelId(channelId);
+                    stringPool.add(channelId);
+                    break;
+                case (int) Notification.CHANNEL_ID_INDEX:
+                    notification.setChannelId(stringPool.get(parser.readInt(
+                            Notification.CHANNEL_ID_INDEX) - 1));
+                    break;
+                case (int) Notification.UID:
+                    notification.setUid(parser.readInt(Notification.UID));
+                    break;
+                case (int) Notification.USER_ID:
+                    notification.setUserId(parser.readInt(Notification.USER_ID));
+                    break;
+                case (int) Notification.POSTED_TIME_MS:
+                    notification.setPostedTimeMs(parser.readLong(Notification.POSTED_TIME_MS));
+                    break;
+                case (int) Notification.TITLE:
+                    notification.setTitle(parser.readString(Notification.TITLE));
+                    break;
+                case (int) Notification.TEXT:
+                    notification.setText(parser.readString(Notification.TEXT));
+                    break;
+                case (int) Notification.ICON:
+                    final long iconToken = parser.start(Notification.ICON);
+                    loadIcon(parser, notification, pkg);
+                    parser.end(iconToken);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return notification.build();
+            }
+        }
+    }
+
+    private static void loadIcon(ProtoInputStream parser,
+            HistoricalNotification.Builder notification, String pkg) throws IOException {
+        int iconType = Notification.TYPE_UNKNOWN;
+        String imageBitmapFileName = null;
+        int imageResourceId = Resources.ID_NULL;
+        String imageResourceIdPackage = null;
+        byte[] imageByteData = null;
+        int imageByteDataLength = 0;
+        int imageByteDataOffset = 0;
+        String imageUri = null;
+
+        while (true) {
+            switch (parser.nextField()) {
+                case (int) Notification.Icon.IMAGE_TYPE:
+                    iconType = parser.readInt(Notification.Icon.IMAGE_TYPE);
+                    break;
+                case (int) Notification.Icon.IMAGE_DATA:
+                    imageByteData = parser.readBytes(Notification.Icon.IMAGE_DATA);
+                    break;
+                case (int) Notification.Icon.IMAGE_DATA_LENGTH:
+                    imageByteDataLength = parser.readInt(Notification.Icon.IMAGE_DATA_LENGTH);
+                    break;
+                case (int) Notification.Icon.IMAGE_DATA_OFFSET:
+                    imageByteDataOffset = parser.readInt(Notification.Icon.IMAGE_DATA_OFFSET);
+                    break;
+                case (int) Notification.Icon.IMAGE_BITMAP_FILENAME:
+                    imageBitmapFileName = parser.readString(
+                            Notification.Icon.IMAGE_BITMAP_FILENAME);
+                    break;
+                case (int) Notification.Icon.IMAGE_RESOURCE_ID:
+                    imageResourceId = parser.readInt(Notification.Icon.IMAGE_RESOURCE_ID);
+                    break;
+                case (int) Notification.Icon.IMAGE_RESOURCE_ID_PACKAGE:
+                    imageResourceIdPackage = parser.readString(
+                            Notification.Icon.IMAGE_RESOURCE_ID_PACKAGE);
+                    break;
+                case (int) Notification.Icon.IMAGE_URI:
+                    imageUri = parser.readString(Notification.Icon.IMAGE_URI);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    if (iconType == Icon.TYPE_DATA) {
+
+                        if (imageByteData != null) {
+                            notification.setIcon(Icon.createWithData(
+                                    imageByteData, imageByteDataOffset, imageByteDataLength));
+                        }
+                    } else if (iconType == Icon.TYPE_RESOURCE) {
+                        if (imageResourceId != Resources.ID_NULL) {
+                            notification.setIcon(Icon.createWithResource(
+                                    imageResourceIdPackage != null
+                                            ? imageResourceIdPackage
+                                            : pkg,
+                                    imageResourceId));
+                        }
+                    } else if (iconType == Icon.TYPE_URI) {
+                        if (imageUri != null) {
+                            notification.setIcon(Icon.createWithContentUri(imageUri));
+                        }
+                    } else if (iconType == Icon.TYPE_BITMAP) {
+                        // TODO: read file from disk
+                    }
+                    return;
+            }
+        }
+    }
+
+    private static void writeIcon(ProtoOutputStream proto, HistoricalNotification notification) {
+        final long token = proto.start(Notification.ICON);
+
+        proto.write(Notification.Icon.IMAGE_TYPE, notification.getIcon().getType());
+        switch (notification.getIcon().getType()) {
+            case Icon.TYPE_DATA:
+                proto.write(Notification.Icon.IMAGE_DATA, notification.getIcon().getDataBytes());
+                proto.write(Notification.Icon.IMAGE_DATA_LENGTH,
+                        notification.getIcon().getDataLength());
+                proto.write(Notification.Icon.IMAGE_DATA_OFFSET,
+                        notification.getIcon().getDataOffset());
+                break;
+            case Icon.TYPE_RESOURCE:
+                proto.write(Notification.Icon.IMAGE_RESOURCE_ID, notification.getIcon().getResId());
+                if (!notification.getPackage().equals(notification.getIcon().getResPackage())) {
+                    proto.write(Notification.Icon.IMAGE_RESOURCE_ID_PACKAGE,
+                            notification.getIcon().getResPackage());
+                }
+                break;
+            case Icon.TYPE_URI:
+                proto.write(Notification.Icon.IMAGE_URI, notification.getIcon().getUriString());
+                break;
+            case Icon.TYPE_BITMAP:
+                // TODO: write file to disk
+                break;
+        }
+
+        proto.end(token);
+    }
+
+    private static void writeNotification(ProtoOutputStream proto,
+            final String[] stringPool, final HistoricalNotification notification) {
+        final long token = proto.start(NotificationHistoryProto.NOTIFICATION);
+        final int packageIndex = Arrays.binarySearch(stringPool, notification.getPackage());
+        if (packageIndex >= 0) {
+            proto.write(Notification.PACKAGE_INDEX, packageIndex + 1);
+        } else {
+            // Package not in Stringpool for some reason, write full string instead
+            Slog.w(TAG, "notification package name (" + notification.getPackage()
+                    + ") not found in string cache");
+            proto.write(Notification.PACKAGE, notification.getPackage());
+        }
+        final int channelNameIndex = Arrays.binarySearch(stringPool, notification.getChannelName());
+        if (channelNameIndex >= 0) {
+            proto.write(Notification.CHANNEL_NAME_INDEX, channelNameIndex + 1);
+        } else {
+            Slog.w(TAG, "notification channel name (" + notification.getChannelName()
+                    + ") not found in string cache");
+            proto.write(Notification.CHANNEL_NAME, notification.getChannelName());
+        }
+        final int channelIdIndex = Arrays.binarySearch(stringPool, notification.getChannelId());
+        if (channelIdIndex >= 0) {
+            proto.write(Notification.CHANNEL_ID_INDEX, channelIdIndex + 1);
+        } else {
+            Slog.w(TAG, "notification channel id (" + notification.getChannelId()
+                    + ") not found in string cache");
+            proto.write(Notification.CHANNEL_ID, notification.getChannelId());
+        }
+        proto.write(Notification.UID, notification.getUid());
+        proto.write(Notification.USER_ID, notification.getUserId());
+        proto.write(Notification.POSTED_TIME_MS, notification.getPostedTimeMs());
+        proto.write(Notification.TITLE, notification.getTitle());
+        proto.write(Notification.TEXT, notification.getText());
+        writeIcon(proto, notification);
+        proto.end(token);
+    }
+
+    public static void read(InputStream in, NotificationHistory notifications,
+            NotificationHistoryFilter filter) throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        List<String> stringPool = new ArrayList<>();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) NotificationHistoryProto.STRING_POOL:
+                    stringPool = readStringPool(proto);
+                    break;
+                case (int) NotificationHistoryProto.NOTIFICATION:
+                    readNotification(proto, stringPool, notifications, filter);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    if (filter.isFiltering()) {
+                        notifications.poolStringsFromNotifications();
+                    } else {
+                        notifications.addPooledStrings(stringPool);
+                    }
+                    return;
+            }
+        }
+    }
+
+    public static void write(OutputStream out, NotificationHistory notifications, int version) {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        proto.write(NotificationHistoryProto.MAJOR_VERSION, version);
+        // String pool should be written before the history itself
+        writeStringPool(proto, notifications);
+
+        List<HistoricalNotification> notificationsToWrite = notifications.getNotificationsToWrite();
+        final int count = notificationsToWrite.size();
+        for (int i = 0; i < count; i++) {
+            writeNotification(proto, notifications.getPooledStringsToWrite(),
+                    notificationsToWrite.get(i));
+        }
+
+        proto.flush();
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index c8179a7..dc0cd18 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -41,7 +41,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.FgThread;
-import com.android.server.compat.CompatConfig;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -131,11 +130,11 @@
 
     private static class FeatureConfigImpl implements FeatureConfig {
         private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
+        private final PackageManagerService.Injector mInjector;
         private volatile boolean mFeatureEnabled = false;
-        private CompatConfig mCompatibility;
 
         private FeatureConfigImpl(PackageManagerService.Injector injector) {
-            mCompatibility = injector.getCompatibility();
+            mInjector = injector;
         }
 
         @Override
@@ -158,7 +157,7 @@
 
         @Override
         public boolean packageIsEnabled(PackageParser.Package pkg) {
-            return mCompatibility.isChangeEnabled(
+            return mInjector.getCompatibility().isChangeEnabled(
                     PackageManager.FILTER_APPLICATION_QUERY, pkg.applicationInfo);
         }
     }
@@ -263,10 +262,10 @@
      * Grants access based on an interaction between a calling and target package, granting
      * visibility of the caller from the target.
      *
-     * @param callingPackage    the package initiating the interaction
-     * @param targetPackage     the package being interacted with and thus gaining visibility of the
-     *                          initiating package.
-     * @param userId            the user in which this interaction was taking place
+     * @param callingPackage the package initiating the interaction
+     * @param targetPackage  the package being interacted with and thus gaining visibility of the
+     *                       initiating package.
+     * @param userId         the user in which this interaction was taking place
      */
     public void grantImplicitAccess(
             String callingPackage, String targetPackage, int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 74a85d5..b36958a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -299,7 +299,7 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.Watchdog;
-import com.android.server.compat.CompatConfig;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.Settings.DatabaseVersion;
@@ -837,7 +837,7 @@
         private final Singleton<StorageManager> mStorageManagerProducer;
         private final Singleton<AppOpsManager> mAppOpsManagerProducer;
         private final Singleton<AppsFilter> mAppsFilterProducer;
-        private final Singleton<CompatConfig> mPlatformCompatProducer;
+        private final Singleton<PlatformCompat> mPlatformCompatProducer;
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
@@ -855,7 +855,7 @@
                 Producer<StorageManager> storageManagerProducer,
                 Producer<AppOpsManager> appOpsManagerProducer,
                 Producer<AppsFilter> appsFilterProducer,
-                Producer<CompatConfig> platformCompatProducer) {
+                Producer<PlatformCompat> platformCompatProducer) {
             mContext = context;
             mLock = lock;
             mInstaller = installer;
@@ -966,7 +966,7 @@
             return mAppsFilterProducer.get(this, mPackageManager);
         }
 
-        public CompatConfig getCompatibility() {
+        public PlatformCompat getCompatibility() {
             return mPlatformCompatProducer.get(this, mPackageManager);
         }
     }
@@ -2356,7 +2356,7 @@
                 new Injector.SystemServiceProducer<>(StorageManager.class),
                 new Injector.SystemServiceProducer<>(AppOpsManager.class),
                 (i, pm) -> AppsFilter.create(i),
-                (i, pm) -> CompatConfig.get());
+                (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"));
 
         PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore);
         t.traceEnd(); // "create package manager"
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ff0dc54..f87175d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2354,7 +2354,7 @@
                     mMovedToFront = true;
                 }
 
-                if (launchStack.topTask() == null) {
+                if (launchStack != null && launchStack.topTask() == null) {
                     // The task does not need to be reparented to the launch stack. Remove the
                     // launch stack if there is no activity in it.
                     Slog.w(TAG, "Removing an empty stack: " + launchStack);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a783ee9..6e238b3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1920,10 +1920,18 @@
             vf.set(displayFrames.mStable);
 
             if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
-                cf.bottom = displayFrames.mContent.bottom;
+                // cf.bottom should not be below the stable bottom, or the content might be obscured
+                // by the navigation bar.
+                if (cf.bottom > displayFrames.mContent.bottom) {
+                    cf.bottom = displayFrames.mContent.bottom;
+                }
             } else {
-                cf.bottom = displayFrames.mDock.bottom;
-                vf.bottom = displayFrames.mContent.bottom;
+                if (cf.bottom > displayFrames.mDock.bottom) {
+                    cf.bottom = displayFrames.mDock.bottom;
+                }
+                if (vf.bottom > displayFrames.mContent.bottom) {
+                    vf.bottom = displayFrames.mContent.bottom;
+                }
             }
         } else {
             dcf.set(displayFrames.mSystem);
diff --git a/services/core/java/com/android/server/wm/PolicyControl.java b/services/core/java/com/android/server/wm/PolicyControl.java
index 4c8ce9e..0f92bc8 100644
--- a/services/core/java/com/android/server/wm/PolicyControl.java
+++ b/services/core/java/com/android/server/wm/PolicyControl.java
@@ -26,6 +26,8 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
@@ -51,7 +53,8 @@
     private static final String TAG = "PolicyControl";
     private static final boolean DEBUG = false;
 
-    private static final String NAME_IMMERSIVE_FULL = "immersive.full";
+    @VisibleForTesting
+    static final String NAME_IMMERSIVE_FULL = "immersive.full";
     private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
     private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
     private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms";
@@ -67,15 +70,19 @@
                 : (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility);
         if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
             vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-                    | View.SYSTEM_UI_FLAG_FULLSCREEN
-                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
+            if (attrs.isFullscreen()) {
+                vis |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            }
             vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                     | View.STATUS_BAR_TRANSLUCENT);
         }
         if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
             vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+            if (attrs.isFullscreen()) {
+                vis |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+            }
             vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                     | View.NAVIGATION_BAR_TRANSLUCENT);
         }
@@ -144,7 +151,8 @@
         }
     }
 
-    private static void setFilters(String value) {
+    @VisibleForTesting
+    static void setFilters(String value) {
         if (DEBUG) Slog.d(TAG, "setFilters: " + value);
         sImmersiveStatusFilter = null;
         sImmersiveNavigationFilter = null;
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index a8c7682..64c7935 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -190,7 +190,7 @@
     }
 }
 
-static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jint strength,
+static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong strength,
                                    jobject vibration) {
     Status status;
     uint32_t lengthMs;
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 2ab8189..e24dec5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -1,7 +1,7 @@
 java_library_static {
     name: "services.net",
     srcs: [
-        ":tethering-services-srcs",
+        ":tethering-servicesnet-srcs",
         "java/**/*.java",
     ],
     static_libs: [
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 4d653b9..d34f783 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -23,6 +23,7 @@
         <option name="test-file-name" value="JobTestApp.apk" />
         <option name="test-file-name" value="ConnTestApp.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
+        <option name="test-file-name" value="SimpleServiceTestApp.apk" />
     </target_preparer>
 
     <option name="test-tag" value="FrameworksServicesTests" />
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1ad7b6e..79af34d 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -191,7 +191,7 @@
     @After
     public void tearDown() throws Exception {
         mHandlerThread.quitSafely();
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(PermissionManagerServiceInternal.class);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 8965152..129d263 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -19,20 +19,34 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.IActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.support.test.uiautomator.UiDevice;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for {@link ActivityManager}.
@@ -43,12 +57,22 @@
 @FlakyTest(detail = "Promote to presubmit if stable")
 @Presubmit
 public class ActivityManagerTest {
+    private static final String TAG = "ActivityManagerTest";
+
+    private static final String TEST_APP = "com.android.servicestests.apps.simpleservicetestapp";
+    private static final String TEST_CLASS = TEST_APP + ".SimpleService";
+    private static final int TEST_LOOPS = 100;
+    private static final long AWAIT_TIMEOUT = 2000;
+    private static final long CHECK_INTERVAL = 100;
 
     private IActivityManager mService;
+    private IRemoteCallback mCallback;
+    private Context mContext;
 
     @Before
     public void setUp() throws Exception {
         mService = ActivityManager.getService();
+        mContext = InstrumentationRegistry.getTargetContext();
     }
 
     @Test
@@ -72,4 +96,112 @@
             }
         }
     }
+
+    @Test
+    public void testServiceUnbindAndKilling() {
+        for (int i = TEST_LOOPS; i > 0; i--) {
+            runOnce(i);
+        }
+    }
+
+    private void runOnce(long yieldDuration) {
+        final PackageManager pm = mContext.getPackageManager();
+        int uid = 0;
+        try {
+            uid = pm.getPackageUid(TEST_APP, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        Intent intent = new Intent();
+        intent.setClassName(TEST_APP, TEST_CLASS);
+
+        // Create a service connection with auto creation.
+        CountDownLatch latch = new CountDownLatch(1);
+        final MyServiceConnection autoConnection = new MyServiceConnection(latch);
+        mContext.bindService(intent, autoConnection, Context.BIND_AUTO_CREATE);
+        try {
+            assertTrue("Timeout to bind to service " + intent.getComponent(),
+                    latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail("Unable to bind to service " + intent.getComponent());
+        }
+
+        // Create a service connection without any flags.
+        intent = new Intent();
+        intent.setClassName(TEST_APP, TEST_CLASS);
+        latch = new CountDownLatch(1);
+        MyServiceConnection otherConnection = new MyServiceConnection(latch);
+        mContext.bindService(intent, otherConnection, 0);
+        try {
+            assertTrue("Timeout to bind to service " + intent.getComponent(),
+                    latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail("Unable to bind to service " + intent.getComponent());
+        }
+
+        // Inform the remote process to kill itself
+        try {
+            mCallback.sendResult(null);
+            // It's basically a test for race condition, we expect the bringDownServiceLocked()
+            // would find out the hosting process is dead - to do this, technically we should
+            // do killing and unbinding simultaneously; but in reality, the killing would take
+            // a little while, before the signal really kills it; so we do it in the same thread,
+            // and even wait a while after sending killing signal.
+            Thread.sleep(yieldDuration);
+        } catch (RemoteException | InterruptedException e) {
+            fail("Unable to kill the process");
+        }
+        // Now unbind that auto connection, this should be equivalent to stopService
+        mContext.unbindService(autoConnection);
+
+        // Now we don't expect the system_server crashes.
+
+        // Wait for the target process dies
+        long total = 0;
+        for (; total < AWAIT_TIMEOUT; total += CHECK_INTERVAL) {
+            try {
+                if (!targetPackageIsRunning(mContext, uid)) {
+                    break;
+                }
+                Thread.sleep(CHECK_INTERVAL);
+            } catch (InterruptedException e) {
+            }
+        }
+        assertTrue("Timeout to wait for the target package dies", total < AWAIT_TIMEOUT);
+        mCallback = null;
+    }
+
+    private boolean targetPackageIsRunning(Context context, int uid) {
+        final String result = runShellCommand(
+                String.format("cmd activity get-uid-state %d", uid));
+        return !result.contains("(NONEXISTENT)");
+    }
+
+    private static String runShellCommand(String cmd) {
+        try {
+            return UiDevice.getInstance(
+                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private class MyServiceConnection implements ServiceConnection {
+        private CountDownLatch mLatch;
+
+        MyServiceConnection(CountDownLatch latch) {
+            this.mLatch = latch;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mCallback = IRemoteCallback.Stub.asInterface(service);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 29a8dad..5c2ad94 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -126,22 +126,6 @@
         doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
     }
 
-    /**
-     * Verify connecting an A2DP sink will call into AudioService to unmute media
-     */
-    @Test
-    public void testA2dpConnectionUnmutesMedia() throws Exception {
-        Log.i(TAG, "testA2dpConnectionUnmutesMedia");
-        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
-
-        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
-                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
-        verify(mMockAudioService, times(1)).postAccessoryPlugMediaUnmute(
-                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-
-    }
-
     private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
             throws Exception {
         when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp b/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp
new file mode 100644
index 0000000..5cbd39c
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp
@@ -0,0 +1,30 @@
+// 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_helper_app {
+    name: "SimpleServiceTestApp",
+
+    test_suites: ["device-tests"],
+
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..8789992
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.simpleservicetestapp">
+
+    <application>
+        <service android:name=".SimpleService"
+                 android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
new file mode 100644
index 0000000..75f71d6
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
@@ -0,0 +1,37 @@
+/*
+ * 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.servicestests.apps.simpleservicetestapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Process;
+
+public class SimpleService extends Service {
+    private final IRemoteCallback.Stub mBinder = new IRemoteCallback.Stub() {
+        @Override
+        public void sendResult(Bundle bundle) {
+            Process.killProcess(Process.myPid());
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
new file mode 100644
index 0000000..bcff2f8
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationHistory.HistoricalNotification;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.util.AtomicFile;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
+
+    File mRootDir;
+    @Mock
+    Handler mFileWriteHandler;
+
+    NotificationHistoryDatabase mDataBase;
+
+    private HistoricalNotification getHistoricalNotification(int index) {
+        return getHistoricalNotification("package" + index, index);
+    }
+
+    private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+        String expectedChannelName = "channelName" + index;
+        String expectedChannelId = "channelId" + index;
+        int expectedUid = 1123456 + index;
+        int expectedUserId = 11 + index;
+        long expectedPostTime = 987654321 + index;
+        String expectedTitle = "title" + index;
+        String expectedText = "text" + index;
+        Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
+                index);
+
+        return new HistoricalNotification.Builder()
+                .setPackage(packageName)
+                .setChannelName(expectedChannelName)
+                .setChannelId(expectedChannelId)
+                .setUid(expectedUid)
+                .setUserId(expectedUserId)
+                .setPostedTimeMs(expectedPostTime)
+                .setTitle(expectedTitle)
+                .setText(expectedText)
+                .setIcon(expectedIcon)
+                .build();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest");
+
+        mDataBase = new NotificationHistoryDatabase(mRootDir);
+        mDataBase.init(mFileWriteHandler);
+    }
+
+    @Test
+    public void testPrune() {
+        int retainDays = 1;
+        for (long i = 10; i >= 5; i--) {
+            File file = mock(File.class);
+            when(file.lastModified()).thenReturn(i);
+            AtomicFile af = new AtomicFile(file);
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(5);
+        cal.add(Calendar.DATE, -1 * retainDays);
+        for (int i = 5; i >= 0; i--) {
+            File file = mock(File.class);
+            when(file.lastModified()).thenReturn(cal.getTimeInMillis() - i);
+            AtomicFile af = new AtomicFile(file);
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        mDataBase.prune(retainDays, 10);
+
+        for (AtomicFile file : mDataBase.mHistoryFiles) {
+            assertThat(file.getBaseFile().lastModified() > 0);
+        }
+    }
+
+    @Test
+    public void testOnPackageRemove_posts() {
+        mDataBase.onPackageRemoved("test");
+        verify(mFileWriteHandler, times(1)).post(any());
+    }
+
+    @Test
+    public void testForceWriteToDisk() {
+        mDataBase.forceWriteToDisk();
+        verify(mFileWriteHandler, times(1)).post(any());
+    }
+
+    @Test
+    public void testOnlyOneWriteRunnableInQueue() {
+        when(mFileWriteHandler.hasCallbacks(any())).thenReturn(true);
+        mDataBase.forceWriteToDisk();
+        verify(mFileWriteHandler, never()).post(any());
+    }
+
+    @Test
+    public void testAddNotification() {
+        HistoricalNotification n = getHistoricalNotification(1);
+        HistoricalNotification n2 = getHistoricalNotification(2);
+
+        mDataBase.addNotification(n);
+        assertThat(mDataBase.mBuffer.getNotificationsToWrite()).contains(n);
+        verify(mFileWriteHandler, times(1)).postDelayed(any(), anyLong());
+
+        // second add should not trigger another write
+        mDataBase.addNotification(n2);
+        assertThat(mDataBase.mBuffer.getNotificationsToWrite()).contains(n2);
+        verify(mFileWriteHandler, times(1)).postDelayed(any(), anyLong());
+    }
+
+    @Test
+    public void testReadNotificationHistory_readsAllFiles() throws Exception {
+        for (long i = 10; i >= 5; i--) {
+            AtomicFile af = mock(AtomicFile.class);
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+
+        mDataBase.readNotificationHistory();
+
+        for (AtomicFile file : mDataBase.mHistoryFiles) {
+            verify(file, times(1)).openRead();
+        }
+    }
+
+    @Test
+    public void testReadNotificationHistory_withNumFilterDoesNotReadExtraFiles() throws Exception {
+        AtomicFile af = mock(AtomicFile.class);
+        when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
+        mDataBase.mHistoryFiles.addLast(af);
+
+        AtomicFile af2 = mock(AtomicFile.class);
+        when(af2.getBaseFile()).thenReturn(new File(mRootDir, "af2"));
+        mDataBase.mHistoryFiles.addLast(af2);
+
+        mDataBase.readNotificationHistory(null, null, 0);
+
+        verify(af, times(1)).openRead();
+        verify(af2, never()).openRead();
+    }
+
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java
new file mode 100644
index 0000000..10bfcf1
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationHistoryFilterTest extends UiServiceTestCase {
+
+    private HistoricalNotification getHistoricalNotification(int index) {
+        return getHistoricalNotification("package" + index, "channelId" + index, index);
+    }
+    private HistoricalNotification getHistoricalNotification(String pkg, int index) {
+        return getHistoricalNotification(pkg, "channelId" + index, index);
+    }
+
+    private HistoricalNotification getHistoricalNotification(String packageName, String channelId,
+            int index) {
+        String expectedChannelName = "channelName" + index;
+        int expectedUid = 1123456 + index;
+        int expectedUserId = 11 + index;
+        long expectedPostTime = 987654321 + index;
+        String expectedTitle = "title" + index;
+        String expectedText = "text" + index;
+        Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
+                index);
+
+        return new HistoricalNotification.Builder()
+                .setPackage(packageName)
+                .setChannelName(expectedChannelName)
+                .setChannelId(channelId)
+                .setUid(expectedUid)
+                .setUserId(expectedUserId)
+                .setPostedTimeMs(expectedPostTime)
+                .setTitle(expectedTitle)
+                .setText(expectedText)
+                .setIcon(expectedIcon)
+                .build();
+    }
+
+    @Test
+    public void testBuilder() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setChannel("pkg", "channel")
+                .setMaxNotifications(3)
+                .build();
+
+        assertThat(filter.getPackage()).isEqualTo("pkg");
+        assertThat(filter.getChannel()).isEqualTo("channel");
+        assertThat(filter.getMaxNotifications()).isEqualTo(3);
+    }
+
+    @Test
+    public void testMatchesCountFilter() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setMaxNotifications(3)
+                .build();
+
+        NotificationHistory history = new NotificationHistory();
+        assertThat(filter.matchesCountFilter(history)).isTrue();
+        history.addNotificationToWrite(getHistoricalNotification(1));
+        assertThat(filter.matchesCountFilter(history)).isTrue();
+        history.addNotificationToWrite(getHistoricalNotification(2));
+        assertThat(filter.matchesCountFilter(history)).isTrue();
+        history.addNotificationToWrite(getHistoricalNotification(3));
+        assertThat(filter.matchesCountFilter(history)).isFalse();
+    }
+
+    @Test
+    public void testMatchesCountFilter_noCountFilter() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .build();
+
+        NotificationHistory history = new NotificationHistory();
+        assertThat(filter.matchesCountFilter(history)).isTrue();
+        history.addNotificationToWrite(getHistoricalNotification(1));
+        assertThat(filter.matchesCountFilter(history)).isTrue();
+    }
+
+    @Test
+    public void testMatchesPackageAndChannelFilter_pkgOnly() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setPackage("pkg")
+                .build();
+
+        HistoricalNotification hnMatches = getHistoricalNotification("pkg", 1);
+        assertThat(filter.matchesPackageAndChannelFilter(hnMatches)).isTrue();
+        HistoricalNotification hnMatches2 = getHistoricalNotification("pkg", 2);
+        assertThat(filter.matchesPackageAndChannelFilter(hnMatches2)).isTrue();
+
+        HistoricalNotification hnNoMatch = getHistoricalNotification("pkg2", 2);
+        assertThat(filter.matchesPackageAndChannelFilter(hnNoMatch)).isFalse();
+    }
+
+    @Test
+    public void testMatchesPackageAndChannelFilter_channelAlso() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setChannel("pkg", "channel")
+                .build();
+
+        HistoricalNotification hn1 = getHistoricalNotification("pkg", 1);
+        assertThat(filter.matchesPackageAndChannelFilter(hn1)).isFalse();
+
+        HistoricalNotification hn2 = getHistoricalNotification("pkg", "channel", 1);
+        assertThat(filter.matchesPackageAndChannelFilter(hn2)).isTrue();
+
+        HistoricalNotification hn3 = getHistoricalNotification("pkg2", "channel", 1);
+        assertThat(filter.matchesPackageAndChannelFilter(hn3)).isFalse();
+    }
+
+    @Test
+    public void testIsFiltering() {
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .build();
+        assertThat(filter.isFiltering()).isFalse();
+
+        filter = new NotificationHistoryFilter.Builder()
+                .setPackage("pkg")
+                .build();
+        assertThat(filter.isFiltering()).isTrue();
+
+        filter = new NotificationHistoryFilter.Builder()
+                .setChannel("pkg", "channel")
+                .build();
+        assertThat(filter.isFiltering()).isTrue();
+
+        filter = new NotificationHistoryFilter.Builder()
+                .setMaxNotifications(5)
+                .build();
+        assertThat(filter.isFiltering()).isTrue();
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
new file mode 100644
index 0000000..458117d
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
@@ -0,0 +1,297 @@
+/*
+ * 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.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.NotificationHistory;
+import android.app.NotificationHistory.HistoricalNotification;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationHistoryProtoHelperTest extends UiServiceTestCase {
+
+    private HistoricalNotification getHistoricalNotification(int index) {
+        return getHistoricalNotification("package" + index, index);
+    }
+
+    private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+        String expectedChannelName = "channelName" + index;
+        String expectedChannelId = "channelId" + index;
+        int expectedUid = 1123456 + index;
+        int expectedUserId = 11 + index;
+        long expectedPostTime = 987654321 + index;
+        String expectedTitle = "title" + index;
+        String expectedText = "text" + index;
+        Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
+                index);
+
+        return new HistoricalNotification.Builder()
+                .setPackage(packageName)
+                .setChannelName(expectedChannelName)
+                .setChannelId(expectedChannelId)
+                .setUid(expectedUid)
+                .setUserId(expectedUserId)
+                .setPostedTimeMs(expectedPostTime)
+                .setTitle(expectedTitle)
+                .setText(expectedText)
+                .setIcon(expectedIcon)
+                .build();
+    }
+
+    @Test
+    public void testReadWriteNotifications() throws Exception {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        // loops backwards just to maintain the post time newest -> oldest expectation
+        for (int i = 10; i >= 1; i--) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedEntries.add(n);
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        NotificationHistory actualHistory = new NotificationHistory();
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                new NotificationHistoryFilter.Builder().build());
+
+        assertThat(actualHistory.getHistoryCount()).isEqualTo(history.getHistoryCount());
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+    }
+
+    @Test
+    public void testReadWriteNotifications_stringFieldsPersistedEvenIfNoPool() throws Exception {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        // loops backwards just to maintain the post time newest -> oldest expectation
+        for (int i = 10; i >= 1; i--) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedEntries.add(n);
+            history.addNotificationToWrite(n);
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        NotificationHistory actualHistory = new NotificationHistory();
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                new NotificationHistoryFilter.Builder().build());
+
+        assertThat(actualHistory.getHistoryCount()).isEqualTo(history.getHistoryCount());
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+    }
+
+    @Test
+    public void testReadNotificationsWithPkgFilter() throws Exception {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        Set<String> expectedStrings = new HashSet<>();
+        // loops backwards just to maintain the post time newest -> oldest expectation
+        for (int i = 10; i >= 1; i--) {
+            HistoricalNotification n =
+                    getHistoricalNotification((i % 2 == 0) ? "pkgEven" : "pkgOdd", i);
+
+            if (i % 2 == 0) {
+                expectedStrings.add(n.getPackage());
+                expectedStrings.add(n.getChannelName());
+                expectedStrings.add(n.getChannelId());
+                expectedEntries.add(n);
+            }
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        NotificationHistory actualHistory = new NotificationHistory();
+
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setPackage("pkgEven")
+                .build();
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                filter);
+
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+        assertThat(Arrays.asList(actualHistory.getPooledStringsToWrite()))
+                .containsExactlyElementsIn(expectedStrings);
+    }
+
+    @Test
+    public void testReadNotificationsWithNumberFilter() throws Exception {
+        int maxCount = 3;
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        Set<String> expectedStrings = new HashSet<>();
+        for (int i = 1; i < 10; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+
+            if (i <= maxCount) {
+                expectedStrings.add(n.getPackage());
+                expectedStrings.add(n.getChannelName());
+                expectedStrings.add(n.getChannelId());
+                expectedEntries.add(n);
+            }
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        NotificationHistory actualHistory = new NotificationHistory();
+
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setMaxNotifications(maxCount)
+                .build();
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                filter);
+
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+        assertThat(Arrays.asList(actualHistory.getPooledStringsToWrite()))
+                .containsExactlyElementsIn(expectedStrings);
+    }
+
+    @Test
+    public void testReadNotificationsWithNumberFilter_preExistingNotifs() throws Exception {
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        Set<String> expectedStrings = new HashSet<>();
+        int maxCount = 3;
+
+        NotificationHistory history = new NotificationHistory();
+        HistoricalNotification old1 = getHistoricalNotification(40);
+        history.addNotificationToWrite(old1);
+        expectedEntries.add(old1);
+
+        HistoricalNotification old2 = getHistoricalNotification(50);
+        history.addNotificationToWrite(old2);
+        expectedEntries.add(old2);
+        history.poolStringsFromNotifications();
+        expectedStrings.addAll(Arrays.asList(history.getPooledStringsToWrite()));
+
+        for (int i = 1; i < 10; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+
+            if (i <= (maxCount - 2)) {
+                expectedStrings.add(n.getPackage());
+                expectedStrings.add(n.getChannelName());
+                expectedStrings.add(n.getChannelId());
+                expectedEntries.add(n);
+            }
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        NotificationHistory actualHistory = new NotificationHistory();
+
+        NotificationHistoryFilter filter = new NotificationHistoryFilter.Builder()
+                .setMaxNotifications(maxCount)
+                .build();
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                filter);
+
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+        assertThat(Arrays.asList(actualHistory.getPooledStringsToWrite()))
+                .containsExactlyElementsIn(expectedStrings);
+    }
+
+    @Test
+    public void testReadMergeIntoExistingHistory() throws Exception {
+        NotificationHistory history = new NotificationHistory();
+
+        List<HistoricalNotification> expectedEntries = new ArrayList<>();
+        Set<String> expectedStrings = new HashSet<>();
+        for (int i = 1; i < 10; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedEntries.add(n);
+            expectedStrings.add(n.getPackage());
+            expectedStrings.add(n.getChannelName());
+            expectedStrings.add(n.getChannelId());
+            history.addNotificationToWrite(n);
+        }
+        history.poolStringsFromNotifications();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NotificationHistoryProtoHelper.write(baos, history, 1);
+
+        // set up pre-existing notification history, as though read from a different file
+        NotificationHistory actualHistory = new NotificationHistory();
+        for (int i = 10; i < 20; i++) {
+            HistoricalNotification n = getHistoricalNotification(i);
+            expectedEntries.add(n);
+            expectedStrings.add(n.getPackage());
+            expectedStrings.add(n.getChannelName());
+            expectedStrings.add(n.getChannelId());
+            actualHistory.addNotificationToWrite(n);
+        }
+        actualHistory.poolStringsFromNotifications();
+
+        NotificationHistoryProtoHelper.read(
+                new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                actualHistory,
+                new NotificationHistoryFilter.Builder().build());
+
+        // Make sure history contains the original and new entries
+        assertThat(actualHistory.getNotificationsToWrite())
+                .containsExactlyElementsIn(expectedEntries);
+        assertThat(Arrays.asList(actualHistory.getPooledStringsToWrite()))
+                .containsExactlyElementsIn(expectedStrings);
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 80439cf..a1322b9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -62,7 +62,6 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
@@ -162,11 +161,11 @@
         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
         contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
 
-        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI)))
                 .thenReturn(CANONICAL_SOUND_URI);
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(CANONICAL_SOUND_URI);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(SOUND_URI);
 
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
@@ -465,7 +464,7 @@
 
         // Testing that in restore we are given the canonical version
         loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
-        verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+        verify(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
     }
 
     @Test
@@ -475,11 +474,11 @@
                 .appendQueryParameter("title", "Test")
                 .appendQueryParameter("canonical", "1")
                 .build();
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(canonicalBasedOnLocal);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(localUri);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+        when(mTestIContentProvider.uncanonicalize(any(), any(), eq(canonicalBasedOnLocal)))
                 .thenReturn(localUri);
 
         NotificationChannel channel =
@@ -499,9 +498,9 @@
     @Test
     public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
         Thread.sleep(3000);
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(null);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(null);
 
         NotificationChannel channel =
@@ -526,7 +525,7 @@
     @Test
     public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
         // Not a local uncanonicalized uri, simulating that it fails to exist locally
-        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI))).thenReturn(null);
         String id = "id";
         String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 2abd340..8774b63 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -132,11 +132,11 @@
         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
         contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
 
-        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI)))
                 .thenReturn(CANONICAL_SOUND_URI);
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(CANONICAL_SOUND_URI);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+        when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(SOUND_URI);
 
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
index 3b336eb..aceed86 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
@@ -12,6 +12,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -109,8 +110,8 @@
         mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken);
         TestableLooper.get(this).processAllMessages();
 
-        verify(mIContentProvider).call(anyString(), anyString(), eq(SliceProvider.METHOD_PIN),
-                eq(null), argThat(b -> {
+        verify(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
+                eq(SliceProvider.METHOD_PIN), eq(null), argThat(b -> {
                     assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
                     return true;
                 }));
@@ -167,8 +168,8 @@
         // Throw exception when trying to pin
         doAnswer(invocation -> {
             throw new Exception("Pin failed");
-        }).when(mIContentProvider).call(
-                anyString(), anyString(), anyString(), eq(null), any());
+        }).when(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
+                anyString(), eq(null), any());
 
         TestableLooper.get(this).processAllMessages();
 
@@ -176,8 +177,8 @@
         mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken);
         TestableLooper.get(this).processAllMessages();
 
-        verify(mIContentProvider).call(anyString(), anyString(), eq(SliceProvider.METHOD_PIN),
-                eq(null), argThat(b -> {
+        verify(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
+                eq(SliceProvider.METHOD_PIN), eq(null), argThat(b -> {
                     assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
                     return true;
                 }));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 2a3731a..67b7a66 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -24,6 +24,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -56,15 +57,23 @@
 import android.view.DisplayInfo;
 import android.view.WindowManager;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Tests for the {@link DisplayPolicy} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayPolicyLayoutTests
+ */
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
@@ -93,6 +102,12 @@
         attrs.format = PixelFormat.TRANSLUCENT;
     }
 
+    @After
+    public void tearDown() {
+        PolicyControl.setFilters("");
+        mWindow.getDisplayContent().mInputMethodTarget = null;
+    }
+
     public void setRotation(int rotation) {
         mRotation = rotation;
         updateDisplayFrames();
@@ -393,6 +408,105 @@
         assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
     }
 
+    @FlakyTest(bugId = 129711077)
+    @Test
+    public void layoutWindowLw_withImmersive_SoftInputAdjustResize() {
+        synchronized (mWm.mGlobalLock) {
+            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
+            mWindow.mAttrs.flags = 0;
+            mWindow.mAttrs.systemUiVisibility =
+                    SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                            | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+
+            addWindow(mWindow);
+
+            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
+            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
+            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
+            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
+        }
+    }
+
+    @FlakyTest(bugId = 129711077)
+    @Test
+    public void layoutWindowLw_withImmersive_SoftInputAdjustNothing() {
+        synchronized (mWm.mGlobalLock) {
+            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
+            mWindow.mAttrs.flags = 0;
+            mWindow.mAttrs.systemUiVisibility =
+                    SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                            | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+
+            addWindow(mWindow);
+
+            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
+            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
+            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), 0, 0);
+        }
+    }
+
+    @FlakyTest(bugId = 129711077)
+    @Test
+    public void layoutWindowLw_withForceImmersive_fullscreen() {
+        synchronized (mWm.mGlobalLock) {
+            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
+            mWindow.mAttrs.flags = 0;
+            mWindow.mAttrs.systemUiVisibility = 0;
+            PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
+
+            addWindow(mWindow);
+
+            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
+            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
+            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
+            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
+        }
+    }
+
+    @FlakyTest(bugId = 129711077)
+    @Test
+    public void layoutWindowLw_withForceImmersive_nonFullscreen() {
+        synchronized (mWm.mGlobalLock) {
+            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
+            mWindow.mAttrs.flags = 0;
+            mWindow.mAttrs.systemUiVisibility = 0;
+            mWindow.mAttrs.width = DISPLAY_WIDTH / 2;
+            mWindow.mAttrs.height = DISPLAY_HEIGHT / 2;
+            PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
+
+            addWindow(mWindow);
+
+            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
+            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
+            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
+            assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, bottomInset);
+            assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
+            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
+        }
+    }
+
     @Test
     public void layoutHint_appWindow() {
         // Initialize DisplayFrames
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 2933b4a..d4558dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -62,6 +62,7 @@
     static final int STATUS_BAR_HEIGHT = 10;
     static final int NAV_BAR_HEIGHT = 15;
     static final int DISPLAY_CUTOUT_HEIGHT = 8;
+    static final int INPUT_METHOD_WINDOW_TOP = 585;
 
     DisplayPolicy mDisplayPolicy;
 
diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING
new file mode 100644
index 0000000..7b53d09
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/TEST_MAPPING
@@ -0,0 +1,33 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.usage"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.usage"
+        },
+        {
+          "exclude-filter": "com.android.server.usage.StorageStatsServiceTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsUsageStatsTestCases",
+      "options": [
+        {
+          "include-filter": "android.app.usage.cts.UsageStatsTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index bc29b59..dc95f16 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -3943,10 +3944,11 @@
     }
 
     /**
-     * Contains received SMS cell broadcast messages. More details are available in 3GPP TS 23.041.
+     * Contains received cell broadcast messages. More details are available in 3GPP TS 23.041.
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final class CellBroadcasts implements BaseColumns {
 
         /**
@@ -3957,11 +3959,44 @@
 
         /**
          * The {@code content://} URI for this table.
+         * Only privileged framework components running on phone or network stack uid can
+         * query or modify this table.
          */
         @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
 
         /**
+         * The {@code content://} URI for query cellbroadcast message history.
+         * query results include following entries
+         * <ul>
+         *     <li>{@link #_ID}</li>
+         *     <li>{@link #SLOT_INDEX}</li>
+         *     <li>{@link #GEOGRAPHICAL_SCOPE}</li>
+         *     <li>{@link #PLMN}</li>
+         *     <li>{@link #LAC}</li>
+         *     <li>{@link #CID}</li>
+         *     <li>{@link #SERIAL_NUMBER}</li>
+         *     <li>{@link #SERVICE_CATEGORY}</li>
+         *     <li>{@link #LANGUAGE_CODE}</li>
+         *     <li>{@link #MESSAGE_BODY}</li>
+         *     <li>{@link #DELIVERY_TIME}</li>
+         *     <li>{@link #MESSAGE_READ}</li>
+         *     <li>{@link #MESSAGE_FORMAT}</li>
+         *     <li>{@link #MESSAGE_PRIORITY}</li>
+         *     <li>{@link #ETWS_WARNING_TYPE}</li>
+         *     <li>{@link #CMAS_MESSAGE_CLASS}</li>
+         *     <li>{@link #CMAS_CATEGORY}</li>
+         *     <li>{@link #CMAS_RESPONSE_TYPE}</li>
+         *     <li>{@link #CMAS_SEVERITY}</li>
+         *     <li>{@link #CMAS_URGENCY}</li>
+         *     <li>{@link #CMAS_CERTAINTY}</li>
+         * </ul>
+         */
+        @RequiresPermission(Manifest.permission.READ_CELL_BROADCASTS)
+        @NonNull
+        public static final Uri MESSAGE_HISTORY_URI = Uri.parse("content://cellbroadcasts/history");
+
+        /**
          * The subscription which received this cell broadcast message.
          * @deprecated use {@link #SLOT_INDEX} instead.
          * <P>Type: INTEGER</P>
@@ -3972,7 +4007,6 @@
         /**
          * The slot which received this cell broadcast message.
          * <P>Type: INTEGER</P>
-         * @hide
          */
         public static final String SLOT_INDEX = "slot_index";
 
@@ -4150,14 +4184,12 @@
         /**
          * The timestamp in millisecond of when the device received the message.
          * <P>Type: BIGINT</P>
-         * @hide
          */
         public static final String RECEIVED_TIME = "received_time";
 
         /**
          * Indicates that whether the message has been broadcasted to the application.
          * <P>Type: BOOLEAN</P>
-         * @hide
          */
         public static final String MESSAGE_BROADCASTED = "message_broadcasted";
 
@@ -4193,7 +4225,6 @@
          * "circle|0,0|100;polygon|0,0|0,1.5|1,1|1,0;circle|100.123,100|200.123"
          *
          * <P>Type: TEXT</P>
-         * @hide
          */
         public static final String GEOMETRIES = "geometries";
 
@@ -4205,7 +4236,6 @@
          * for the alert.
          *
          * <P>Type: INTEGER</P>
-         * @hide
          */
         public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 58e15b9..7bab223 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3224,6 +3224,14 @@
     public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY =
             "disconnect_cause_play_busytone_int_array";
 
+    /**
+     * Flag specifying whether to prevent sending CLIR activation("*31#") and deactivation("#31#")
+     * code only without dialing number.
+     * When {@code true}, these are prevented, {@code false} otherwise.
+     */
+    public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL =
+            "prevent_clir_activation_and_deactivation_code_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -3657,6 +3665,7 @@
         sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
         sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
                 new int[] {4 /* BUSY */});
+        sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2eb4809..8455e3d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1873,7 +1873,12 @@
         if (telephony == null) return null;
 
         try {
-            return telephony.getMeidForSlot(slotIndex, getOpPackageName());
+            String meid = telephony.getMeidForSlot(slotIndex, getOpPackageName());
+            if (TextUtils.isEmpty(meid)) {
+                Log.d(TAG, "getMeid: return null because MEID is not available");
+                return null;
+            }
+            return meid;
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -9507,10 +9512,12 @@
     }
 
     /**
-     * Resets telephony manager settings back to factory defaults.
+     * Resets Telephony and IMS settings back to factory defaults.
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONNECTIVITY_INTERNAL)
     public void factoryReset(int subId) {
         try {
             Log.d(TAG, "factoryReset: subId=" + subId);
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 2161dcb..a5d6266 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1191,7 +1191,7 @@
             && !other.canHandleType(TYPE_DUN)
             && Objects.equals(this.mApnName, other.mApnName)
             && !typeSameAny(this, other)
-            && xorEquals(this.mProxyAddress, other.mProxyAddress)
+            && xorEqualsString(this.mProxyAddress, other.mProxyAddress)
             && xorEqualsInt(this.mProxyPort, other.mProxyPort)
             && xorEquals(this.mProtocol, other.mProtocol)
             && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
@@ -1200,7 +1200,7 @@
             && Objects.equals(this.mMvnoType, other.mMvnoType)
             && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
             && xorEquals(this.mMmsc, other.mMmsc)
-            && xorEquals(this.mMmsProxyAddress, other.mMmsProxyAddress)
+            && xorEqualsString(this.mMmsProxyAddress, other.mMmsProxyAddress)
             && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
             && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
             && Objects.equals(mApnSetId, other.mApnSetId)
@@ -1213,6 +1213,11 @@
         return first == null || second == null || first.equals(second);
     }
 
+    // Equal or one is null.
+    private boolean xorEqualsString(String first, String second) {
+        return TextUtils.isEmpty(first) || TextUtils.isEmpty(second) || first.equals(second);
+    }
+
     // Equal or one is not specified.
     private boolean xorEqualsInt(int first, int second) {
         return first == UNSPECIFIED_INT || second == UNSPECIFIED_INT
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 2fad847..ed292be 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -25,14 +25,13 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
@@ -42,6 +41,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
 
 import java.lang.annotation.Retention;
@@ -49,6 +49,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * A manager for the MmTel (Multimedia Telephony) feature of an IMS network, given an associated
@@ -64,8 +65,6 @@
 @SystemApi
 public class ImsMmTelManager {
 
-    private static final String TAG = "ImsMmTelManager";
-
     /**
      * @hide
      */
@@ -311,7 +310,7 @@
         }
     }
 
-    private int mSubId;
+    private final int mSubId;
 
     /**
      * Create an instance of {@link ImsMmTelManager} for the subscription id specified.
@@ -366,10 +365,6 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        if (!isImsAvailableOnDevice()) {
-            throw new ImsException("IMS not available on device.",
-                    ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
-        }
         c.setExecutor(executor);
         try {
             getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
@@ -378,7 +373,7 @@
                 // Rethrow as runtime error to keep API compatible.
                 throw new IllegalArgumentException(e.getMessage());
             } else {
-                throw new RuntimeException(e.getMessage());
+                throw new ImsException(e.getMessage(), e.errorCode);
             }
         } catch (RemoteException | IllegalStateException e) {
             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
@@ -441,10 +436,6 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        if (!isImsAvailableOnDevice()) {
-            throw new ImsException("IMS not available on device.",
-                    ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
-        }
         c.setExecutor(executor);
         try {
             getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
@@ -453,7 +444,7 @@
                 // Rethrow as runtime error to keep API compatible.
                 throw new IllegalArgumentException(e.getMessage());
             } else {
-                throw new RuntimeException(e.getMessage());
+                throw new ImsException(e.getMessage(), e.errorCode);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -618,6 +609,46 @@
     }
 
     /**
+     * Query whether or not the requested MmTel capability is supported by the carrier on the
+     * specified network transport.
+     * <p>
+     * This is a configuration option and does not change. The only time this may change is if a
+     * new IMS configuration is loaded when there is a
+     * {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} broadcast for this subscription.
+     * @param capability The capability that is being queried for support on the carrier network.
+     * @param transportType The transport type of the capability to check support for.
+     * @param callback A consumer containing a Boolean result specifying whether or not the
+     *                 capability is supported on this carrier network for the transport specified.
+     * @param executor The executor that the callback will be called with.
+     * @throws ImsException if the subscription is no longer valid or the IMS service is not
+     * available.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void isSupported(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+            @AccessNetworkConstants.TransportType int transportType,
+            @NonNull Consumer<Boolean> callback,
+            @NonNull @CallbackExecutor Executor executor) throws ImsException {
+        if (callback == null) {
+            throw new IllegalArgumentException("Must include a non-null Consumer.");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must include a non-null Executor.");
+        }
+        try {
+            getITelephony().isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    executor.execute(() -> callback.accept(result == 1));
+                }
+            }, capability, transportType);
+        } catch (ServiceSpecificException sse) {
+            throw new ImsException(sse.getMessage(), sse.errorCode);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * The user's setting for whether or not they have enabled the "Video Calling" setting.
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
@@ -940,7 +971,7 @@
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    boolean isTtyOverVolteEnabled() {
+    public boolean isTtyOverVolteEnabled() {
         try {
             return getITelephony().isTtyOverVolteEnabled(mSubId);
         } catch (ServiceSpecificException e) {
@@ -955,20 +986,39 @@
         }
     }
 
-    private static boolean isImsAvailableOnDevice() {
-        IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-        if (pm == null) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
-            return true;
+    /**
+     * Get the status of the MmTel Feature registered on this subscription.
+     * @param callback A callback containing an Integer describing the current state of the
+     *                 MmTel feature, Which will be one of the following:
+     *                 {@link ImsFeature#STATE_UNAVAILABLE},
+     *                {@link ImsFeature#STATE_INITIALIZING},
+     *                {@link ImsFeature#STATE_READY}. Will be called using the executor
+     *                 specified when the service state has been retrieved from the IMS service.
+     * @param executor The executor that will be used to call the callback.
+     * @throws ImsException if the IMS service associated with this subscription is not available or
+     * the IMS service is not available.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void getFeatureState(@NonNull @ImsFeature.ImsState Consumer<Integer> callback,
+            @NonNull @CallbackExecutor Executor executor) throws ImsException {
+        if (callback == null) {
+            throw new IllegalArgumentException("Must include a non-null Consumer.");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must include a non-null Executor.");
         }
         try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+            getITelephony().getImsMmTelFeatureState(mSubId, new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    executor.execute(() -> callback.accept(result));
+                }
+            });
+        } catch (ServiceSpecificException sse) {
+            throw new ImsException(sse.getMessage(), sse.errorCode);
         } catch (RemoteException e) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
+            e.rethrowAsRuntimeException();
         }
-        return true;
     }
 
     private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index ceb4704..8b27b6f 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -362,6 +362,25 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProcessCallResult {}
 
+    /**
+     * If the flag is present and true, it indicates that the incoming call is for USSD.
+     * <p>
+     * This is an optional boolean flag.
+     */
+    public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";
+
+    /**
+     * If this flag is present and true, this call is marked as an unknown dialing call instead
+     * of an incoming call. An example of such a call is a call that is originated by sending
+     * commands (like AT commands) directly to the modem without Android involvement or dialing
+     * calls appearing over IMS when the modem does a silent redial from circuit-switched to IMS in
+     * certain situations.
+     * <p>
+     * This is an optional boolean flag.
+     */
+    public static final String EXTRA_IS_UNKNOWN_CALL =
+            "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
+
     private IImsMmTelListener mListener;
 
     /**
@@ -410,6 +429,8 @@
     /**
      * Notify the framework of an incoming call.
      * @param c The {@link ImsCallSessionImplBase} of the new incoming call.
+     * @param extras A bundle containing extra parameters related to the call. See
+     * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above.
      */
     public final void notifyIncomingCall(@NonNull ImsCallSessionImplBase c,
             @NonNull Bundle extras) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f79a5c6..b2f9add 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -867,6 +867,11 @@
     String getImsService(int slotId, boolean isCarrierImsService);
 
     /**
+     * Get the MmTelFeature state attached to this subscription id.
+     */
+    void getImsMmTelFeatureState(int subId, IIntegerConsumer callback);
+
+    /**
      * Set the network selection mode to automatic.
      *
      * @param subId the id of the subscription to update.
@@ -1815,6 +1820,12 @@
     boolean isAvailable(int subId, int capability, int regTech);
 
     /**
+     * Return whether or not the MmTel capability is supported for the requested transport type.
+     */
+    void isMmTelCapabilitySupported(int subId, IIntegerConsumer callback, int capability,
+            int transportType);
+
+    /**
      * Returns true if the user's setting for 4G LTE is enabled, for the subscription specified.
      */
     boolean isAdvancedCallingSettingEnabled(int subId);
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index 4d8c7d9..9d3e120 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -56,21 +56,22 @@
      */
     private class InversionIContentProvider implements IContentProvider {
         @Override
-        public ContentProviderResult[] applyBatch(String callingPackage, String authority,
+        public ContentProviderResult[] applyBatch(String callingPackage,
+                @Nullable String featureId, String authority,
                 ArrayList<ContentProviderOperation> operations)
                 throws RemoteException, OperationApplicationException {
             return MockContentProvider.this.applyBatch(authority, operations);
         }
 
         @Override
-        public int bulkInsert(String callingPackage, Uri url, ContentValues[] initialValues)
-                throws RemoteException {
+        public int bulkInsert(String callingPackage, @Nullable String featureId, Uri url,
+                ContentValues[] initialValues) throws RemoteException {
             return MockContentProvider.this.bulkInsert(url, initialValues);
         }
 
         @Override
-        public int delete(String callingPackage, Uri url, String selection, String[] selectionArgs)
-                throws RemoteException {
+        public int delete(String callingPackage, @Nullable String featureId, Uri url,
+                String selection, String[] selectionArgs) throws RemoteException {
             return MockContentProvider.this.delete(url, selection, selectionArgs);
         }
 
@@ -80,42 +81,42 @@
         }
 
         @Override
-        public Uri insert(String callingPackage, Uri url, ContentValues initialValues)
-                throws RemoteException {
+        public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
+                ContentValues initialValues) throws RemoteException {
             return MockContentProvider.this.insert(url, initialValues);
         }
 
         @Override
-        public AssetFileDescriptor openAssetFile(
-                String callingPackage, Uri url, String mode, ICancellationSignal signal)
+        public AssetFileDescriptor openAssetFile(String callingPackage,
+                @Nullable String featureId, Uri url, String mode, ICancellationSignal signal)
                 throws RemoteException, FileNotFoundException {
             return MockContentProvider.this.openAssetFile(url, mode);
         }
 
         @Override
-        public ParcelFileDescriptor openFile(
-                String callingPackage, Uri url, String mode, ICancellationSignal signal,
-                IBinder callerToken) throws RemoteException, FileNotFoundException {
+        public ParcelFileDescriptor openFile(String callingPackage, @Nullable String featureId,
+                Uri url, String mode, ICancellationSignal signal, IBinder callerToken)
+                throws RemoteException, FileNotFoundException {
             return MockContentProvider.this.openFile(url, mode);
         }
 
         @Override
-        public Cursor query(String callingPackage, Uri url, @Nullable String[] projection,
-                @Nullable Bundle queryArgs,
-                @Nullable ICancellationSignal cancellationSignal)
-                throws RemoteException {
+        public Cursor query(String callingPackage, @Nullable String featureId, Uri url,
+                @Nullable String[] projection, @Nullable Bundle queryArgs,
+                @Nullable ICancellationSignal cancellationSignal) throws RemoteException {
             return MockContentProvider.this.query(url, projection, queryArgs, null);
         }
 
         @Override
-        public int update(String callingPackage, Uri url, ContentValues values, String selection,
-                String[] selectionArgs) throws RemoteException {
+        public int update(String callingPackage, @Nullable String featureId, Uri url,
+                ContentValues values, String selection, String[] selectionArgs)
+                throws RemoteException {
             return MockContentProvider.this.update(url, values, selection, selectionArgs);
         }
 
         @Override
-        public Bundle call(String callingPackage, String authority, String method, String request,
-                Bundle args) throws RemoteException {
+        public Bundle call(String callingPackage, @Nullable String featureId, String authority,
+                String method, String request, Bundle args) throws RemoteException {
             return MockContentProvider.this.call(authority, method, request, args);
         }
 
@@ -130,9 +131,9 @@
         }
 
         @Override
-        public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri url,
-                String mimeType, Bundle opts, ICancellationSignal signal)
-                throws RemoteException, FileNotFoundException {
+        public AssetFileDescriptor openTypedAssetFile(String callingPackage,
+                @Nullable String featureId, Uri url, String mimeType, Bundle opts,
+                ICancellationSignal signal) throws RemoteException, FileNotFoundException {
             return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
         }
 
@@ -142,23 +143,26 @@
         }
 
         @Override
-        public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+        public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+                throws RemoteException {
             return MockContentProvider.this.canonicalize(uri);
         }
 
         @Override
-        public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+        public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+                throws RemoteException {
             return MockContentProvider.this.uncanonicalize(uri);
         }
 
         @Override
-        public boolean refresh(String callingPkg, Uri url, Bundle args,
-                ICancellationSignal cancellationSignal) throws RemoteException {
+        public boolean refresh(String callingPkg, @Nullable String featureId, Uri url,
+                Bundle args, ICancellationSignal cancellationSignal) throws RemoteException {
             return MockContentProvider.this.refresh(url, args);
         }
 
         @Override
-        public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+        public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri,
+                int uid, int modeFlags) {
             return MockContentProvider.this.checkUriPermission(uri, uid, modeFlags);
         }
     }
diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java
index b072d74..e512b52 100644
--- a/test-mock/src/android/test/mock/MockIContentProvider.java
+++ b/test-mock/src/android/test/mock/MockIContentProvider.java
@@ -16,14 +16,12 @@
 
 package android.test.mock;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentValues;
 import android.content.EntityIterator;
 import android.content.IContentProvider;
-import android.content.Intent;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
@@ -45,14 +43,15 @@
  */
 public class MockIContentProvider implements IContentProvider {
     @Override
-    public int bulkInsert(String callingPackage, Uri url, ContentValues[] initialValues) {
+    public int bulkInsert(String callingPackage, @Nullable String featureId, Uri url,
+            ContentValues[] initialValues) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
     @SuppressWarnings("unused")
-    public int delete(String callingPackage, Uri url, String selection, String[] selectionArgs)
-            throws RemoteException {
+    public int delete(String callingPackage, @Nullable String featureId, Uri url,
+            String selection, String[] selectionArgs) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
@@ -63,33 +62,33 @@
 
     @Override
     @SuppressWarnings("unused")
-    public Uri insert(String callingPackage, Uri url, ContentValues initialValues)
-            throws RemoteException {
+    public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
+            ContentValues initialValues) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public ParcelFileDescriptor openFile(
-            String callingPackage, Uri url, String mode, ICancellationSignal signal,
-            IBinder callerToken) {
+    public ParcelFileDescriptor openFile(String callingPackage, @Nullable String featureId,
+            Uri url, String mode, ICancellationSignal signal, IBinder callerToken) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public AssetFileDescriptor openAssetFile(
-            String callingPackage, Uri uri, String mode, ICancellationSignal signal) {
+    public AssetFileDescriptor openAssetFile(String callingPackage, @Nullable String featureId,
+            Uri uri, String mode, ICancellationSignal signal) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public ContentProviderResult[] applyBatch(String callingPackage, String authority,
-            ArrayList<ContentProviderOperation> operations) {
+    public ContentProviderResult[] applyBatch(String callingPackage, @Nullable String featureId,
+            String authority, ArrayList<ContentProviderOperation> operations) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public Cursor query(String callingPackage, Uri url, @Nullable String[] projection,
-            @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
+    public Cursor query(String callingPackage, @Nullable String featureId, Uri url,
+            @Nullable String[] projection, @Nullable Bundle queryArgs,
+            @Nullable ICancellationSignal cancellationSignal) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
@@ -99,14 +98,14 @@
     }
 
     @Override
-    public int update(String callingPackage, Uri url, ContentValues values, String selection,
-            String[] selectionArgs) throws RemoteException {
+    public int update(String callingPackage, @Nullable String featureId, Uri url,
+            ContentValues values, String selection, String[] selectionArgs) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public Bundle call(String callingPackage, String authority, String method, String request,
-            Bundle args) throws RemoteException {
+    public Bundle call(String callingPackage, @Nullable String featureId, String authority,
+            String method, String request, Bundle args) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
@@ -121,8 +120,9 @@
     }
 
     @Override
-    public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri url, String mimeType,
-            Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+    public AssetFileDescriptor openTypedAssetFile(String callingPackage,
+            @Nullable String featureId, Uri url, String mimeType, Bundle opts,
+            ICancellationSignal signal) throws RemoteException, FileNotFoundException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
@@ -132,24 +132,27 @@
     }
 
     @Override
-    public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+    public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+            throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+    public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+            throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
-    public boolean refresh(String callingPkg, Uri url, Bundle args,
+    public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle args,
             ICancellationSignal cancellationSignal) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     /** {@hide} */
     @Override
-    public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+    public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri, int uid,
+            int modeFlags) {
         throw new UnsupportedOperationException("unimplemented mock method call");
     }
 }
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index b158476..fd92c65 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -14,6 +14,8 @@
 
 package android.testing;
 
+import static org.junit.Assert.assertEquals;
+
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.os.Bundle;
@@ -25,8 +27,6 @@
 
 import java.util.HashMap;
 
-import static org.junit.Assert.*;
-
 /**
  * Allows calls to android.provider.Settings to be tested easier.
  *
@@ -71,7 +71,7 @@
 
     public Bundle call(String method, String arg, Bundle extras) {
         // Methods are "GET_system", "GET_global", "PUT_secure", etc.
-        final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, 0);
+        final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId());
         final String[] commands = method.split("_", 2);
         final String op = commands[0];
         final String table = commands[1];
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 3bedddc..30c24d3 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -34,6 +34,7 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.BackupUtils;
 import android.util.Log;
@@ -730,6 +731,14 @@
     public String lastUpdateName;
 
     /**
+     * The carrier ID identifies the operator who provides this network configuration.
+     *    see {@link TelephonyManager#getSimCarrierId()}
+     * @hide
+     */
+    @SystemApi
+    public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+    /**
      * @hide
      * Status of user approval for connection
      */
@@ -1046,10 +1055,10 @@
 
     /**
      * @hide
-     * The wall clock time of when |mRandomizedMacAddress| last changed.
-     * Used to determine when we should re-randomize in aggressive mode.
+     * The wall clock time of when |mRandomizedMacAddress| should be re-randomized in aggressive
+     * randomization mode.
      */
-    public long randomizedMacLastModifiedTimeMs = 0;
+    public long randomizedMacExpirationTimeMs = 0;
 
     /**
      * @hide
@@ -1850,6 +1859,7 @@
                 .append(" PRIO: ").append(this.priority)
                 .append(" HIDDEN: ").append(this.hiddenSSID)
                 .append(" PMF: ").append(this.requirePMF)
+                .append("CarrierId: ").append(this.carrierId)
                 .append('\n');
 
 
@@ -1910,8 +1920,9 @@
         }
         sbuf.append(" macRandomizationSetting: ").append(macRandomizationSetting).append("\n");
         sbuf.append(" mRandomizedMacAddress: ").append(mRandomizedMacAddress).append("\n");
-        sbuf.append(" randomizedMacLastModifiedTimeMs: ").append(randomizedMacLastModifiedTimeMs)
-                .append("\n");
+        sbuf.append(" randomizedMacExpirationTimeMs: ")
+                .append(randomizedMacExpirationTimeMs == 0 ? "<none>"
+                        : TimeUtils.logTimeOfDay(randomizedMacExpirationTimeMs)).append("\n");
         sbuf.append(" KeyMgmt:");
         for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
             if (this.allowedKeyManagement.get(k)) {
@@ -2439,9 +2450,10 @@
             recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
             mRandomizedMacAddress = source.mRandomizedMacAddress;
             macRandomizationSetting = source.macRandomizationSetting;
-            randomizedMacLastModifiedTimeMs = source.randomizedMacLastModifiedTimeMs;
+            randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
             requirePMF = source.requirePMF;
             updateIdentifier = source.updateIdentifier;
+            carrierId = source.carrierId;
         }
     }
 
@@ -2515,7 +2527,8 @@
         dest.writeParcelable(mRandomizedMacAddress, flags);
         dest.writeInt(macRandomizationSetting);
         dest.writeInt(osu ? 1 : 0);
-        dest.writeLong(randomizedMacLastModifiedTimeMs);
+        dest.writeLong(randomizedMacExpirationTimeMs);
+        dest.writeInt(carrierId);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -2591,7 +2604,8 @@
                 config.mRandomizedMacAddress = in.readParcelable(null);
                 config.macRandomizationSetting = in.readInt();
                 config.osu = in.readInt() != 0;
-                config.randomizedMacLastModifiedTimeMs = in.readLong();
+                config.randomizedMacExpirationTimeMs = in.readLong();
+                config.carrierId = in.readInt();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index f8c2011..7b99a2b 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -1263,4 +1263,23 @@
     public @Ocsp int getOcsp() {
         return mOcsp;
     }
+
+    /**
+     * If the current authentication method needs SIM card.
+     * @return true if the credential information require SIM card for current authentication
+     * method, otherwise it returns false.
+     * @hide
+     */
+    public boolean requireSimCredential() {
+        if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) {
+            return true;
+        }
+        if (mEapMethod == Eap.PEAP) {
+            if (mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
+                    || mPhase2Method == Phase2.AKA_PRIME) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9b529ce..246e96f 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -21,12 +21,15 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.app.ActivityThread;
 import android.net.MacAddress;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import java.nio.charset.CharsetEncoder;
@@ -107,6 +110,12 @@
          */
         private int mPriority;
 
+        /**
+         * The carrier ID identifies the operator who provides this network configuration.
+         *    see {@link TelephonyManager#getSimCarrierId()}
+         */
+        private int mCarrierId;
+
         public Builder() {
             mSsid = null;
             mBssid =  null;
@@ -121,6 +130,7 @@
             mIsUserInteractionRequired = false;
             mIsMetered = false;
             mPriority = UNASSIGNED_PRIORITY;
+            mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         }
 
         /**
@@ -258,6 +268,23 @@
         }
 
         /**
+         * Set the carrier ID of the network operator. The carrier ID associates a Suggested
+         * network with a specific carrier (and therefore SIM). The carrier ID must be provided
+         * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA,
+         * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication.
+         * @param carrierId see {@link TelephonyManager#getSimCarrierId()}.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
+        public @NonNull Builder setCarrierId(int carrierId) {
+            mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
          * Specifies whether this represents a hidden network.
          * <p>
          * <li>If not set, defaults to false (i.e not a hidden network).</li>
@@ -380,6 +407,7 @@
             wifiConfiguration.meteredOverride =
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
                             : WifiConfiguration.METERED_OVERRIDE_NONE;
+            wifiConfiguration.carrierId = mCarrierId;
             return wifiConfiguration;
         }
 
@@ -405,6 +433,7 @@
             wifiConfiguration.meteredOverride =
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
                             : WifiConfiguration.METERED_OVERRIDE_NONE;
+            mPasspointConfiguration.setCarrierId(mCarrierId);
             return wifiConfiguration;
         }
 
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index e9aa076..5befb54 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -398,6 +399,30 @@
     }
 
     /**
+     * The carrier ID identifies the operator who provides this network configuration.
+     *    see {@link TelephonyManager#getSimCarrierId()}
+     */
+    private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+    /**
+     * Set the carrier ID associated with current configuration.
+     * @param carrierId {@code mCarrierId}
+     * @hide
+     */
+    public void setCarrierId(int carrierId) {
+        this.mCarrierId = carrierId;
+    }
+
+    /**
+     * Get the carrier ID associated with current configuration.
+     * @return {@code mCarrierId}
+     * @hide
+     */
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    /**
      * Constructor for creating PasspointConfiguration with default values.
      */
     public PasspointConfiguration() {}
@@ -438,6 +463,7 @@
         mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
         mServiceFriendlyNames = source.mServiceFriendlyNames;
         mAaaServerTrustedNames = source.mAaaServerTrustedNames;
+        mCarrierId = source.mCarrierId;
     }
 
     @Override
@@ -466,6 +492,7 @@
         bundle.putSerializable("serviceFriendlyNames",
                 (HashMap<String, String>) mServiceFriendlyNames);
         dest.writeBundle(bundle);
+        dest.writeInt(mCarrierId);
     }
 
     @Override
@@ -495,6 +522,7 @@
                 && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
+                && mCarrierId == that.mCarrierId
                 && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
                 : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
     }
@@ -505,7 +533,7 @@
                 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
                 mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
-                mServiceFriendlyNames);
+                mServiceFriendlyNames, mCarrierId);
     }
 
     @Override
@@ -558,6 +586,7 @@
         if (mServiceFriendlyNames != null) {
             builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
         }
+        builder.append("CarrierId:" + mCarrierId);
         return builder.toString();
     }
 
@@ -662,6 +691,7 @@
                 Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
                         "serviceFriendlyNames");
                 config.setServiceFriendlyNames(friendlyNamesMap);
+                config.mCarrierId = in.readInt();
                 return config;
             }