Merge "Give ServiceWatcher a proper lifecycle"
diff --git a/Android.bp b/Android.bp
index 6b39b51..73bc382 100644
--- a/Android.bp
+++ b/Android.bp
@@ -620,6 +620,7 @@
         "av-types-aidl-java",
         "mediatranscoding_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
+        "modules-utils-os",
     ],
 }
 
@@ -1259,7 +1260,6 @@
 filegroup {
     name: "framework-telephony-common-shared-srcs",
     srcs: [
-        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/os/RegistrantList.java",
         "core/java/android/os/Registrant.java",
         "core/java/android/util/IndentingPrintWriter.java",
@@ -1342,7 +1342,6 @@
     name: "framework-wifi-service-shared-srcs",
     srcs: [
         "core/java/android/net/InterfaceConfiguration.java",
-        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/util/BackupUtils.java",
         "core/java/android/util/Rational.java",
         "core/java/com/android/internal/util/FastXmlSerializer.java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1e72062..cc20213 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -20,10 +20,11 @@
 import android.app.AppGlobals;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.os.BasicShellCommandHandler;
 import android.os.Binder;
 import android.os.UserHandle;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintWriter;
 
 public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
diff --git a/api/current.txt b/api/current.txt
index 344e93b..2ebb75d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7403,6 +7403,12 @@
     field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
 
+  public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
+  }
+
 }
 
 package android.app.assist {
@@ -43126,10 +43132,11 @@
     method public String getKeystoreAlias();
     method public int getOrigin();
     method public int getPurposes();
+    method public int getSecurityLevel();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
     method public int getUserAuthenticationValidityDurationSeconds();
-    method public boolean isInsideSecureHardware();
+    method @Deprecated public boolean isInsideSecureHardware();
     method public boolean isInvalidatedByBiometricEnrollment();
     method public boolean isTrustedUserPresenceRequired();
     method public boolean isUserAuthenticationRequired();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0eaebce..73602ae 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4190,6 +4190,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback);
+    field public static final String FUSED_PROVIDER = "fused";
   }
 
   public final class LocationRequest implements android.os.Parcelable {
@@ -5091,6 +5092,7 @@
     method public long getAvSyncTime(int);
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
+    method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
     method public int linkFrontendToCiCam(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
@@ -5307,6 +5309,7 @@
   public class Filter implements java.lang.AutoCloseable {
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
+    method public int configureScramblingStatusEvent(int);
     method public int flush();
     method public int getId();
     method public long getId64Bit();
@@ -5314,6 +5317,9 @@
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int start();
     method public int stop();
+    field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
+    field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
+    field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
     field public static final int STATUS_DATA_READY = 1; // 0x1
     field public static final int STATUS_HIGH_WATER = 4; // 0x4
     field public static final int STATUS_LOW_WATER = 2; // 0x2
@@ -5414,7 +5420,7 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
-    method public int getFirstMbInSlice();
+    method public int getFirstMacroblockInSlice();
     method public int getMpuSequenceNumber();
     method public long getPts();
     method public int getScHevcIndexMask();
@@ -5492,6 +5498,10 @@
     method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setTsIndexMask(int);
   }
 
+  public final class ScramblingStatusEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getScramblingStatus();
+  }
+
   public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
     method public int getSectionNumber();
@@ -5590,7 +5600,7 @@
 
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
-    method public int getFirstMbInSlice();
+    method public int getFirstMacroblockInSlice();
     method public int getPacketId();
     method public long getPts();
     method public int getScIndexMask();
@@ -9919,8 +9929,13 @@
     ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable);
   }
 
+  public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+    method public int getNamespace();
+  }
+
   public static final class KeyGenParameterSpec.Builder {
-    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
+    method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
   }
 
 }
@@ -11202,6 +11217,17 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
   }
 
+  public final class CarrierBandwidth implements android.os.Parcelable {
+    ctor public CarrierBandwidth(int, int, int, int);
+    method public int describeContents();
+    method public int getPrimaryDownlinkCapacityKbps();
+    method public int getPrimaryUplinkCapacityKbps();
+    method public int getSecondaryDownlinkCapacityKbps();
+    method public int getSecondaryUplinkCapacityKbps();
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR;
+    field public static final int INVALID = -1; // 0xffffffff
+  }
+
   public class CarrierConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -11499,6 +11525,18 @@
     field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class PinResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAttemptsRemaining();
+    method public int getResult();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+    field public static final int PIN_RESULT_TYPE_ABORTED = 3; // 0x3
+    field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+    field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+    field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+  }
+
   public final class PreciseCallState implements android.os.Parcelable {
     ctor public PreciseCallState(int, int, int, int, int);
     method public int describeContents();
@@ -11842,6 +11880,7 @@
 
   public class TelephonyManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
     method public int checkCarrierPrivilegesForPackage(String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(String);
     method public void dial(String);
@@ -11855,6 +11894,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -11946,6 +11986,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method public int setNrDualConnectivityState(int);
@@ -11961,10 +12002,12 @@
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 012450d..d225f966 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -294,6 +294,7 @@
         "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
         "tests/e2e/Attribution_e2e_test.cpp",
         "tests/e2e/ConfigTtl_e2e_test.cpp",
+        "tests/e2e/ConfigUpdate_e2e_test.cpp",
         "tests/e2e/CountMetric_e2e_test.cpp",
         "tests/e2e/DurationMetric_e2e_test.cpp",
         "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 7bee4e2..ae1ef21 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -520,10 +520,10 @@
 }
 
 void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
-                                        const StatsdConfig& config) {
+                                        const StatsdConfig& config, bool modularUpdate) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS);
-    OnConfigUpdatedLocked(timestampNs, key, config);
+    OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate);
 }
 
 void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key,
@@ -720,7 +720,8 @@
     for (const auto& key : configs) {
         StatsdConfig config;
         if (StorageManager::readConfigFromDisk(key, &config)) {
-            OnConfigUpdatedLocked(timestampNs, key, config);
+            // Force a full update when resetting a config.
+            OnConfigUpdatedLocked(timestampNs, key, config, /*modularUpdate=*/false);
             StatsdStats::getInstance().noteConfigReset(key);
         } else {
             ALOGE("Failed to read backup config from disk for : %s", key.ToString().c_str());
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 383dbd9..2af277a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -48,7 +48,7 @@
     void OnLogEvent(LogEvent* event);
 
     void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
-                         const StatsdConfig& config);
+                         const StatsdConfig& config, bool modularUpdate = false);
     void OnConfigRemoved(const ConfigKey& key);
 
     size_t GetMetricsSize(const ConfigKey& key) const;
@@ -188,7 +188,7 @@
     void resetIfConfigTtlExpiredLocked(const int64_t timestampNs);
 
     void OnConfigUpdatedLocked(const int64_t currentTimestampNs, const ConfigKey& key,
-                               const StatsdConfig& config, bool modularUpdate = false);
+                               const StatsdConfig& config, bool modularUpdate);
 
     void GetActiveConfigsLocked(const int uid, vector<int64_t>& outActiveConfigs);
 
@@ -338,6 +338,10 @@
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
+    FRIEND_TEST(ConfigUpdateE2eTest, TestHashStrings);
+    FRIEND_TEST(ConfigUpdateE2eTest, TestUidMapVersionStringInstaller);
+    FRIEND_TEST(ConfigUpdateE2eTest, TestConfigTtl);
+
     FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
diff --git a/cmds/statsd/src/config/ConfigListener.h b/cmds/statsd/src/config/ConfigListener.h
index dcd5e52..3d30137 100644
--- a/cmds/statsd/src/config/ConfigListener.h
+++ b/cmds/statsd/src/config/ConfigListener.h
@@ -39,7 +39,7 @@
      * A configuration was added or updated.
      */
     virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
-                                 const StatsdConfig& config) = 0;
+                                 const StatsdConfig& config, bool modularUpdate = false) = 0;
 
     /**
      * A configuration was removed.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index acc12aa..3b6e10b 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -188,6 +188,23 @@
     mAlertTrackerMap = newAlertTrackerMap;
     mAllPeriodicAlarmTrackers = newPeriodicAlarmTrackers;
 
+    mTtlNs = config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1;
+    refreshTtl(currentTimeNs);
+
+    mHashStringsInReport = config.hash_strings_in_metric_report();
+    mVersionStringsInReport = config.version_strings_in_metric_report();
+    mInstallerInReport = config.installer_in_metric_report();
+    mWhitelistedAtomIds.clear();
+    mWhitelistedAtomIds.insert(config.whitelisted_atom_ids().begin(),
+                               config.whitelisted_atom_ids().end());
+    mShouldPersistHistory = config.persist_locally();
+
+    // Store the sub-configs used.
+    mAnnotations.clear();
+    for (const auto& annotation : config.annotation()) {
+        mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32());
+    }
+
     mAllowedUid.clear();
     mAllowedPkg.clear();
     mDefaultPullUids.clear();
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 23048ae..98d4bff 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -172,7 +172,7 @@
     bool mVersionStringsInReport = false;
     bool mInstallerInReport = false;
 
-    const int64_t mTtlNs;
+    int64_t mTtlNs;
     int64_t mTtlEndNs;
 
     int64_t mLastReportTimeNs;
@@ -193,7 +193,7 @@
     // To guard access to mAllowedLogSources
     mutable std::mutex mAllowedLogSourcesMutex;
 
-    const std::set<int32_t> mWhitelistedAtomIds;
+    std::set<int32_t> mWhitelistedAtomIds;
 
     // We can pull any atom from these uids.
     std::set<int32_t> mDefaultPullUids;
@@ -211,8 +211,7 @@
     // Contains the annotations passed in with StatsdConfig.
     std::list<std::pair<const int64_t, const int32_t>> mAnnotations;
 
-    const bool mShouldPersistHistory;
-
+    bool mShouldPersistHistory;
 
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
@@ -327,6 +326,7 @@
 
     FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
     FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric);
+    FRIEND_TEST(ConfigUpdateE2eTest, TestConfigTtl);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 6372361..af9606b 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -869,6 +869,14 @@
         newMetricProducers.push_back(producer.value());
     }
 
+    for (int i = 0; i < config.no_report_metric_size(); ++i) {
+        const int64_t noReportMetric = config.no_report_metric(i);
+        if (newMetricProducerMap.find(noReportMetric) == newMetricProducerMap.end()) {
+            ALOGW("no_report_metric %" PRId64 " not exist", noReportMetric);
+            return false;
+        }
+        noReportMetricIds.insert(noReportMetric);
+    }
     const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
                                           config.whitelisted_atom_ids().end());
     for (int i = 0; i < allMetricsCount; i++) {
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 9455304..1d83716 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -44,8 +44,8 @@
  */
 class MockListener : public ConfigListener {
 public:
-    MOCK_METHOD3(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key,
-                                       const StatsdConfig& config));
+    MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key,
+                                       const StatsdConfig& config, bool modularUpdate));
     MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key));
 };
 
@@ -89,26 +89,26 @@
         manager->StartupForTest();
 
         // Add another one
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")),
-            StatsdConfigEq(91)))
+        EXPECT_CALL(*(listener.get()),
+                    OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), _))
                 .RetiresOnSaturation();
         manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91);
 
         // Update It
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")),
-            StatsdConfigEq(92)))
+        EXPECT_CALL(*(listener.get()),
+                    OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), _))
                 .RetiresOnSaturation();
         manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92);
 
         // Add one with the same uid but a different name
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")),
-            StatsdConfigEq(93)))
+        EXPECT_CALL(*(listener.get()),
+                    OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), _))
                 .RetiresOnSaturation();
         manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93);
 
         // Add one with the same name but a different uid
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")),
-            StatsdConfigEq(94)))
+        EXPECT_CALL(*(listener.get()),
+                    OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), _))
                 .RetiresOnSaturation();
         manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94);
 
@@ -143,7 +143,7 @@
 
     StatsdConfig config;
 
-    EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _)).Times(5);
+    EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, _)).Times(5);
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx"))));
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy"))));
     EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz"))));
diff --git a/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
new file mode 100644
index 0000000..e01a0b6
--- /dev/null
+++ b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -0,0 +1,307 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include <thread>  // std::this_thread::sleep_for
+
+#include "android-base/stringprintf.h"
+#include "src/StatsLogProcessor.h"
+#include "src/storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+#define STATS_DATA_DIR "/data/misc/stats-data"
+using android::base::StringPrintf;
+
+namespace {
+
+StatsdConfig CreateSimpleConfig() {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_STATSD");
+    config.set_hash_strings_in_metric_report(false);
+
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+    // Simple count metric so the config isn't empty.
+    CountMetric* countMetric1 = config.add_count_metric();
+    countMetric1->set_id(StringToId("Count1"));
+    countMetric1->set_what(config.atom_matcher(0).id());
+    countMetric1->set_bucket(FIVE_MINUTES);
+    return config;
+}
+}  // namespace
+
+// Setup for parameterized tests.
+class ConfigUpdateE2eTest : public TestWithParam<bool> {};
+
+INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eTest, ConfigUpdateE2eTest, testing::Bool());
+
+TEST_P(ConfigUpdateE2eTest, TestUidMapVersionStringInstaller) {
+    sp<UidMap> uidMap = new UidMap();
+    vector<int32_t> uids({1000});
+    vector<int64_t> versions({1});
+    vector<String16> apps({String16("app1")});
+    vector<String16> versionStrings({String16("v1")});
+    vector<String16> installers({String16("installer1")});
+    uidMap->updateMap(1, uids, versions, versionStrings, apps, installers);
+
+    StatsdConfig config = CreateSimpleConfig();
+    config.set_version_strings_in_metric_report(true);
+    config.set_installer_in_metric_report(false);
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+
+    // Now update.
+    config.set_version_strings_in_metric_report(false);
+    config.set_installer_in_metric_report(true);
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam());
+    EXPECT_TRUE(metricsManager->isConfigValid());
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    // First report is written to disk when the update happens.
+    ASSERT_EQ(reports.reports_size(), 2);
+    UidMapping uidMapping = reports.reports(1).uid_map();
+    ASSERT_EQ(uidMapping.snapshots_size(), 1);
+    ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
+    EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string());
+    EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1");
+}
+
+TEST_P(ConfigUpdateE2eTest, TestHashStrings) {
+    sp<UidMap> uidMap = new UidMap();
+    vector<int32_t> uids({1000});
+    vector<int64_t> versions({1});
+    vector<String16> apps({String16("app1")});
+    vector<String16> versionStrings({String16("v1")});
+    vector<String16> installers({String16("installer1")});
+    uidMap->updateMap(1, uids, versions, versionStrings, apps, installers);
+
+    StatsdConfig config = CreateSimpleConfig();
+    config.set_version_strings_in_metric_report(true);
+    config.set_hash_strings_in_metric_report(true);
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+
+    // Now update.
+    config.set_hash_strings_in_metric_report(false);
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam());
+    EXPECT_TRUE(metricsManager->isConfigValid());
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    // First report is written to disk when the update happens.
+    ASSERT_EQ(reports.reports_size(), 2);
+    UidMapping uidMapping = reports.reports(1).uid_map();
+    ASSERT_EQ(uidMapping.snapshots_size(), 1);
+    ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
+    EXPECT_TRUE(uidMapping.snapshots(0).package_info(0).has_version_string());
+    EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string_hash());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestAnnotations) {
+    StatsdConfig config = CreateSimpleConfig();
+    StatsdConfig_Annotation* annotation = config.add_annotation();
+    annotation->set_field_int64(11);
+    annotation->set_field_int32(1);
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+
+    // Now update
+    config.clear_annotation();
+    annotation = config.add_annotation();
+    annotation->set_field_int64(22);
+    annotation->set_field_int32(2);
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    // First report is written to disk when the update happens.
+    ASSERT_EQ(reports.reports_size(), 2);
+    ConfigMetricsReport report = reports.reports(1);
+    EXPECT_EQ(report.annotation_size(), 1);
+    EXPECT_EQ(report.annotation(0).field_int64(), 22);
+    EXPECT_EQ(report.annotation(0).field_int32(), 2);
+}
+
+TEST_P(ConfigUpdateE2eTest, TestPersistLocally) {
+    StatsdConfig config = CreateSimpleConfig();
+    config.set_persist_locally(false);
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 1);
+    // Number of reports should still be 1 since persist_locally is false.
+    reports.Clear();
+    buffer.clear();
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    // Now update.
+    config.set_persist_locally(true);
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+    // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk.
+    reports.Clear();
+    buffer.clear();
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 2);
+    // Should get 3, 2 on disk + 1 in memory.
+    reports.Clear();
+    buffer.clear();
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 3);
+    string suffix = StringPrintf("%d_%lld", cfgKey.GetUid(), (long long)cfgKey.GetId());
+    StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, suffix.c_str());
+    string historySuffix =
+            StringPrintf("%d_%lld_history", cfgKey.GetUid(), (long long)cfgKey.GetId());
+    StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, historySuffix.c_str());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestNoReportMetrics) {
+    StatsdConfig config = CreateSimpleConfig();
+    // Second simple count metric.
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(StringToId("Count2"));
+    countMetric->set_what(config.atom_matcher(0).id());
+    countMetric->set_bucket(FIVE_MINUTES);
+    config.add_no_report_metric(config.count_metric(0).id());
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+
+    // Now update.
+    config.clear_no_report_metric();
+    config.add_no_report_metric(config.count_metric(1).id());
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    // First report is written to disk when the update happens.
+    ASSERT_EQ(reports.reports_size(), 2);
+    // First report (before update) has the first count metric.
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).metric_id(), config.count_metric(1).id());
+    // Second report (after update) has the first count metric.
+    ASSERT_EQ(reports.reports(1).metrics_size(), 1);
+    EXPECT_EQ(reports.reports(1).metrics(0).metric_id(), config.count_metric(0).id());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestAtomsAllowedFromAnyUid) {
+    StatsdConfig config = CreateSimpleConfig();
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+    // Uses AID_ROOT, which isn't in allowed log sources.
+    unique_ptr<LogEvent> event = CreateBatteryStateChangedEvent(
+            baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+    processor->OnLogEvent(event.get());
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 1001, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 1);
+    // Check the metric and make sure it has 0 count.
+    ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+    EXPECT_FALSE(reports.reports(0).metrics(0).has_count_metrics());
+
+    // Now update. Allow plugged state to be logged from any uid, so the atom will be counted.
+    config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED);
+    processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+    unique_ptr<LogEvent> event2 = CreateBatteryStateChangedEvent(
+            baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+    processor->OnLogEvent(event.get());
+    reports.Clear();
+    buffer.clear();
+    processor->onDumpReport(cfgKey, baseTimeNs + 3000, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 2);
+    // Check the metric and make sure it has 0 count.
+    ASSERT_EQ(reports.reports(1).metrics_size(), 1);
+    EXPECT_TRUE(reports.reports(1).metrics(0).has_count_metrics());
+    ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data_size(), 1);
+    ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
+    EXPECT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+}
+
+TEST_P(ConfigUpdateE2eTest, TestConfigTtl) {
+    StatsdConfig config = CreateSimpleConfig();
+    config.set_ttl_in_seconds(1);
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    ConfigKey cfgKey(0, 12345);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC);
+
+    config.set_ttl_in_seconds(5);
+    processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config,
+                               /*modularUpdate=*/GetParam());
+    metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC);
+
+    // Clear the data stored on disk as a result of the update.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, baseTimeNs + 3 * NS_PER_SEC, false, true, ADB_DUMP, FAST,
+                            &buffer);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/config/preloaded-classes b/config/preloaded-classes
index f56656b..ecf11c2 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4532,7 +4532,6 @@
 android.os.BadParcelableException
 android.os.BaseBundle$NoImagePreloadHolder
 android.os.BaseBundle
-android.os.BasicShellCommandHandler
 android.os.BatteryManager
 android.os.BatteryManagerInternal
 android.os.BatteryProperty$1
@@ -9558,6 +9557,7 @@
 com.android.internal.widget.VerifyCredentialResponse
 com.android.internal.widget.ViewClippingUtil$ClippingParameters
 com.android.internal.widget.ViewClippingUtil
+com.android.module.utils.BasicShellCommandHandler
 com.android.okhttp.Address
 com.android.okhttp.AndroidShimResponseCache
 com.android.okhttp.Authenticator
diff --git a/core/api/current.txt b/core/api/current.txt
index 9562715..31264b9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7403,6 +7403,12 @@
     field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
 
+  public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
+  }
+
 }
 
 package android.app.assist {
@@ -41238,10 +41244,11 @@
     method public String getKeystoreAlias();
     method public int getOrigin();
     method public int getPurposes();
+    method public int getSecurityLevel();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
     method public int getUserAuthenticationValidityDurationSeconds();
-    method public boolean isInsideSecureHardware();
+    method @Deprecated public boolean isInsideSecureHardware();
     method public boolean isInvalidatedByBiometricEnrollment();
     method public boolean isTrustedUserPresenceRequired();
     method public boolean isUserAuthenticationRequired();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ed3b7de..37a5389 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4130,6 +4130,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback);
+    field public static final String FUSED_PROVIDER = "fused";
   }
 
   public final class LocationRequest implements android.os.Parcelable {
@@ -5031,6 +5032,7 @@
     method public long getAvSyncTime(int);
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
+    method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
     method public int linkFrontendToCiCam(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
@@ -5247,6 +5249,7 @@
   public class Filter implements java.lang.AutoCloseable {
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
+    method public int configureScramblingStatusEvent(int);
     method public int flush();
     method public int getId();
     method public long getId64Bit();
@@ -5254,6 +5257,9 @@
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int start();
     method public int stop();
+    field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
+    field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
+    field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
     field public static final int STATUS_DATA_READY = 1; // 0x1
     field public static final int STATUS_HIGH_WATER = 4; // 0x4
     field public static final int STATUS_LOW_WATER = 2; // 0x2
@@ -5354,7 +5360,7 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
-    method public int getFirstMbInSlice();
+    method public int getFirstMacroblockInSlice();
     method public int getMpuSequenceNumber();
     method public long getPts();
     method public int getScHevcIndexMask();
@@ -5432,6 +5438,10 @@
     method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setTsIndexMask(int);
   }
 
+  public final class ScramblingStatusEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getScramblingStatus();
+  }
+
   public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
     method public int getSectionNumber();
@@ -5530,7 +5540,7 @@
 
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
-    method public int getFirstMbInSlice();
+    method public int getFirstMacroblockInSlice();
     method public int getPacketId();
     method public long getPts();
     method public int getScIndexMask();
@@ -8760,8 +8770,13 @@
     ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable);
   }
 
+  public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+    method public int getNamespace();
+  }
+
   public static final class KeyGenParameterSpec.Builder {
-    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
+    method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
   }
 
 }
@@ -10043,6 +10058,17 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
   }
 
+  public final class CarrierBandwidth implements android.os.Parcelable {
+    ctor public CarrierBandwidth(int, int, int, int);
+    method public int describeContents();
+    method public int getPrimaryDownlinkCapacityKbps();
+    method public int getPrimaryUplinkCapacityKbps();
+    method public int getSecondaryDownlinkCapacityKbps();
+    method public int getSecondaryUplinkCapacityKbps();
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR;
+    field public static final int INVALID = -1; // 0xffffffff
+  }
+
   public class CarrierConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -10340,6 +10366,18 @@
     field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class PinResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAttemptsRemaining();
+    method public int getResult();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+    field public static final int PIN_RESULT_TYPE_ABORTED = 3; // 0x3
+    field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+    field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+    field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+  }
+
   public final class PreciseCallState implements android.os.Parcelable {
     ctor public PreciseCallState(int, int, int, int, int);
     method public int describeContents();
@@ -10683,6 +10721,7 @@
 
   public class TelephonyManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
     method public int checkCarrierPrivilegesForPackage(String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(String);
     method public void dial(String);
@@ -10696,6 +10735,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -10787,6 +10827,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method public int setNrDualConnectivityState(int);
@@ -10802,10 +10843,12 @@
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7a74ad1..ef4dfdf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,12 +369,17 @@
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+    field public static final int OPERATION_LOCK_NOW = 1; // 0x1
   }
 
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
     ctor public SecurityLog.SecurityEvent(long, byte[]);
   }
 
+  public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+    method public int getOperation();
+  }
+
 }
 
 package android.app.blob {
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 32b61b5..5dc6e60 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -16,16 +16,23 @@
 package android.accounts;
 
 import android.app.Activity;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.view.View;
-import android.view.LayoutInflater;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
 import com.android.internal.R;
 
 import java.io.IOException;
@@ -42,6 +49,7 @@
     private Account mAccount;
     private String mAuthTokenType;
     private int mUid;
+    private int mCallingUid;
     private Bundle mResultBundle = null;
     protected LayoutInflater mInflater;
 
@@ -77,6 +85,20 @@
             return;
         }
 
+        try {
+            IBinder activityToken = getActivityToken();
+            mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken);
+        } catch (RemoteException re) {
+            // Couldn't figure out caller details
+            Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
+        }
+
+        if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) && mCallingUid != mUid) {
+            setResult(Activity.RESULT_CANCELED);
+            finish();
+            return;
+        }
+
         String accountTypeLabel;
         try {
             accountTypeLabel = getAccountLabel(mAccount);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1d644c4..01f3932 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -122,6 +122,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
+// TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
 /**
  * Public interface for managing policies enforced on a device. Most clients of this class must be
  * registered with the system as a <a href="{@docRoot}guide/topics/admin/device-admin.html">device
@@ -130,6 +131,13 @@
  * for that method specifies that it is restricted to either device or profile owners. Any
  * application calling an api may only pass as an argument a device administrator component it
  * owns. Otherwise, a {@link SecurityException} will be thrown.
+ *
+ * <p><b>Note: </b>on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, some methods can
+ * throw an {@link UnsafeStateException} exception (for example, if the vehicle is moving), so
+ * callers running on automotive builds should wrap every method call under the methods provided by
+ * {@code android.car.admin.CarDevicePolicyManager}.
+ *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>
@@ -1972,6 +1980,7 @@
     public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
 
     /**
+     * TODO (b/137101239): clean up split system user codes
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
@@ -1995,6 +2004,7 @@
     public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
 
     /**
+     * TODO (b/137101239): clean up split system user codes
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
@@ -2443,6 +2453,19 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface PersonalAppsSuspensionReason {}
 
+    /** @hide */
+    @TestApi
+    public static final int OPERATION_LOCK_NOW = 1;
+
+    // TODO(b/172376923) - add all operations
+    /** @hide */
+    @IntDef(prefix = "OPERATION_", value = {
+            OPERATION_LOCK_NOW,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public static @interface DevicePolicyOperation {
+    }
+
     /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
new file mode 100644
index 0000000..1f8a933
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+
+/**
+ * Interface responsible to check if a {@link DevicePolicyManager} API can be safely executed.
+ *
+ * @hide
+ */
+public interface DevicePolicySafetyChecker {
+
+    /**
+     * Returns whether the given {@code operation} can be safely executed at the moment.
+     */
+    default boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+        return true;
+    }
+
+    /**
+     * Returns a new exception for when the given {@code operation} cannot be safely executed.
+     */
+    @NonNull
+    default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) {
+        return new UnsafeStateException(operation);
+    }
+}
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
new file mode 100644
index 0000000..9dcaae4
--- /dev/null
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when a {@link DevicePolicyManager} operation failed because it was not safe
+ * to be executed at that moment.
+ *
+ * <p>For example, it can be thrown on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive devices} when the vehicle
+ * is moving.
+ */
+@SuppressWarnings("serial")
+public final class UnsafeStateException extends IllegalStateException implements Parcelable {
+
+    private final @DevicePolicyOperation int mOperation;
+
+    /** @hide */
+    public UnsafeStateException(@DevicePolicyOperation int operation) {
+        super();
+
+        mOperation = operation;
+    }
+
+    /** @hide */
+    @TestApi
+    public @DevicePolicyOperation int getOperation() {
+        return mOperation;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mOperation);
+    }
+
+    @NonNull
+    public static final Creator<UnsafeStateException> CREATOR =
+            new Creator<UnsafeStateException>() {
+
+        @Override
+        public UnsafeStateException createFromParcel(Parcel source) {
+            return new UnsafeStateException(source.readInt());
+        }
+
+        @Override
+        public UnsafeStateException[] newArray(int size) {
+            return new UnsafeStateException[size];
+        }
+    };
+}
diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS
index 7e7710b..0f88811 100644
--- a/core/java/android/app/backup/OWNERS
+++ b/core/java/android/app/backup/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 656484
 
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
 
diff --git a/core/java/android/hardware/iris/IIrisService.aidl b/core/java/android/hardware/iris/IIrisService.aidl
index b9eca3b..3d26318 100644
--- a/core/java/android/hardware/iris/IIrisService.aidl
+++ b/core/java/android/hardware/iris/IIrisService.aidl
@@ -22,5 +22,5 @@
  */
 interface IIrisService {
     // Give IrisService its ID. See AuthService.java
-    void initializeConfiguration(int sensorId);
+    void initializeConfiguration(int sensorId, int strength);
 }
diff --git a/core/java/android/os/BasicShellCommandHandler.java b/core/java/android/os/BasicShellCommandHandler.java
deleted file mode 100644
index 366da3d..0000000
--- a/core/java/android/os/BasicShellCommandHandler.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-/**
- * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
- * be copied into mainline modules, so this class must not use any hidden APIs.
- *
- * @hide
- */
-public abstract class BasicShellCommandHandler {
-    static final String TAG = "ShellCommand";
-    static final boolean DEBUG = false;
-
-    private Binder mTarget;
-    private FileDescriptor mIn;
-    private FileDescriptor mOut;
-    private FileDescriptor mErr;
-    private String[] mArgs;
-
-    private String mCmd;
-    private int mArgPos;
-    private String mCurArgData;
-
-    private FileInputStream mFileIn;
-    private FileOutputStream mFileOut;
-    private FileOutputStream mFileErr;
-
-    private PrintWriter mOutPrintWriter;
-    private PrintWriter mErrPrintWriter;
-    private InputStream mInputStream;
-
-    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, int firstArgPos) {
-        mTarget = target;
-        mIn = in;
-        mOut = out;
-        mErr = err;
-        mArgs = args;
-        mCmd = null;
-        mArgPos = firstArgPos;
-        mCurArgData = null;
-        mFileIn = null;
-        mFileOut = null;
-        mFileErr = null;
-        mOutPrintWriter = null;
-        mErrPrintWriter = null;
-        mInputStream = null;
-    }
-
-    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args) {
-        String cmd;
-        int start;
-        if (args != null && args.length > 0) {
-            cmd = args[0];
-            start = 1;
-        } else {
-            cmd = null;
-            start = 0;
-        }
-        init(target, in, out, err, args, start);
-        mCmd = cmd;
-
-        if (DEBUG) {
-            RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
-            Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
-            Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
-                    + " pid=" + Binder.getCallingPid());
-        }
-        int res = -1;
-        try {
-            res = onCommand(mCmd);
-            if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
-        } catch (Throwable e) {
-            // Unlike usual calls, in this case if an exception gets thrown
-            // back to us we want to print it back in to the dump data, since
-            // that is where the caller expects all interesting information to
-            // go.
-            PrintWriter eout = getErrPrintWriter();
-            eout.println();
-            eout.println("Exception occurred while executing '" + mCmd + "':");
-            e.printStackTrace(eout);
-        } finally {
-            if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
-            if (mOutPrintWriter != null) {
-                mOutPrintWriter.flush();
-            }
-            if (mErrPrintWriter != null) {
-                mErrPrintWriter.flush();
-            }
-            if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
-        }
-        if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
-        return res;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the output stream.
-     */
-    public FileDescriptor getOutFileDescriptor() {
-        return mOut;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's output data stream.
-     */
-    public OutputStream getRawOutputStream() {
-        if (mFileOut == null) {
-            mFileOut = new FileOutputStream(mOut);
-        }
-        return mFileOut;
-    }
-
-    /**
-     * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
-     */
-    public PrintWriter getOutPrintWriter() {
-        if (mOutPrintWriter == null) {
-            mOutPrintWriter = new PrintWriter(getRawOutputStream());
-        }
-        return mOutPrintWriter;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the error stream.
-     */
-    public FileDescriptor getErrFileDescriptor() {
-        return mErr;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's error output data stream.
-     */
-    public OutputStream getRawErrorStream() {
-        if (mFileErr == null) {
-            mFileErr = new FileOutputStream(mErr);
-        }
-        return mFileErr;
-    }
-
-    /**
-     * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
-     */
-    public PrintWriter getErrPrintWriter() {
-        if (mErr == null) {
-            return getOutPrintWriter();
-        }
-        if (mErrPrintWriter == null) {
-            mErrPrintWriter = new PrintWriter(getRawErrorStream());
-        }
-        return mErrPrintWriter;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the input stream.
-     */
-    public FileDescriptor getInFileDescriptor() {
-        return mIn;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's input data stream.
-     */
-    public InputStream getRawInputStream() {
-        if (mFileIn == null) {
-            mFileIn = new FileInputStream(mIn);
-        }
-        return mFileIn;
-    }
-
-    /**
-     * Return buffered access to the command's {@link #getRawInputStream()}.
-     */
-    public InputStream getBufferedInputStream() {
-        if (mInputStream == null) {
-            mInputStream = new BufferedInputStream(getRawInputStream());
-        }
-        return mInputStream;
-    }
-
-    /**
-     * Return the next option on the command line -- that is an argument that
-     * starts with '-'.  If the next argument is not an option, null is returned.
-     */
-    public String getNextOption() {
-        if (mCurArgData != null) {
-            String prev = mArgs[mArgPos - 1];
-            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
-        }
-        if (mArgPos >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mArgPos];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mArgPos++;
-        if (arg.equals("--")) {
-            return null;
-        }
-        if (arg.length() > 1 && arg.charAt(1) != '-') {
-            if (arg.length() > 2) {
-                mCurArgData = arg.substring(2);
-                return arg.substring(0, 2);
-            } else {
-                mCurArgData = null;
-                return arg;
-            }
-        }
-        mCurArgData = null;
-        return arg;
-    }
-
-    /**
-     * Return the next argument on the command line, whatever it is; if there are
-     * no arguments left, return null.
-     */
-    public String getNextArg() {
-        if (mCurArgData != null) {
-            String arg = mCurArgData;
-            mCurArgData = null;
-            return arg;
-        } else if (mArgPos < mArgs.length) {
-            return mArgs[mArgPos++];
-        } else {
-            return null;
-        }
-    }
-
-    public String peekNextArg() {
-        if (mCurArgData != null) {
-            return mCurArgData;
-        } else if (mArgPos < mArgs.length) {
-            return mArgs[mArgPos];
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * @return all the remaining arguments in the command without moving the current position.
-     */
-    public String[] peekRemainingArgs() {
-        int remaining = getRemainingArgsCount();
-        String[] args = new String[remaining];
-        for (int pos = mArgPos; pos < mArgs.length; pos++) {
-            args[pos - mArgPos] = mArgs[pos];
-        }
-        return args;
-    }
-
-    /**
-     * Returns number of arguments that haven't been processed yet.
-     */
-    public int getRemainingArgsCount() {
-        if (mArgPos >= mArgs.length) {
-            return 0;
-        }
-        return mArgs.length - mArgPos;
-    }
-
-    /**
-     * Return the next argument on the command line, whatever it is; if there are
-     * no arguments left, throws an IllegalArgumentException to report this to the user.
-     */
-    public String getNextArgRequired() {
-        String arg = getNextArg();
-        if (arg == null) {
-            String prev = mArgs[mArgPos - 1];
-            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
-        }
-        return arg;
-    }
-
-    public int handleDefaultCommands(String cmd) {
-        if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
-            onHelp();
-        } else {
-            getOutPrintWriter().println("Unknown command: " + cmd);
-        }
-        return -1;
-    }
-
-    public Binder getTarget() {
-        return mTarget;
-    }
-
-    public String[] getAllArgs() {
-        return mArgs;
-    }
-
-    /**
-     * Implement parsing and execution of a command.  If it isn't a command you understand,
-     * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
-     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
-     * to process additional command line arguments.  Command output can be written to
-     * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
-     *
-     * <p class="caution">Note that no permission checking has been done before entering this
-     * function, so you need to be sure to do your own security verification for any commands you
-     * are executing.  The easiest way to do this is to have the ShellCommand contain
-     * only a reference to your service's aidl interface, and do all of your command
-     * implementations on top of that -- that way you can rely entirely on your executing security
-     * code behind that interface.</p>
-     *
-     * @param cmd The first command line argument representing the name of the command to execute.
-     * @return Return the command result; generally 0 or positive indicates success and
-     * negative values indicate error.
-     */
-    public abstract int onCommand(String cmd);
-
-    /**
-     * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
-     */
-    public abstract void onHelp();
-}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 3358ce1..a2173a6 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,15 +19,9 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Slog;
 
-import com.android.internal.util.FastPrintWriter;
+import com.android.modules.utils.BasicShellCommandHandler;
 
-import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
 
 /**
  * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b94031a..46a5caf 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -40,7 +40,9 @@
 import android.media.AudioFormat;
 import android.media.permission.Identity;
 import android.os.AsyncTask;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -246,6 +248,7 @@
     private final Callback mExternalCallback;
     private final Object mLock = new Object();
     private final Handler mHandler;
+    private final IBinder mBinder = new Binder();
 
     private int mAvailability = STATE_NOT_READY;
 
@@ -450,7 +453,7 @@
             Identity identity = new Identity();
             identity.packageName = ActivityThread.currentOpPackageName();
             mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator(
-                    identity);
+                    identity, mBinder);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
diff --git a/core/java/android/uwb/RangingParams.java b/core/java/android/uwb/RangingParams.java
deleted file mode 100644
index f23d9ed..0000000
--- a/core/java/android/uwb/RangingParams.java
+++ /dev/null
@@ -1,477 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * An object used when requesting to open a new {@link RangingSession}.
- * <p>Use {@link RangingParams.Builder} to create an instance of this class.
- *
- *  @hide
- */
-public final class RangingParams implements Parcelable {
-    private final boolean mIsInitiator;
-    private final boolean mIsController;
-    private final Duration mSamplePeriod;
-    private final UwbAddress mLocalDeviceAddress;
-    private final List<UwbAddress> mRemoteDeviceAddresses;
-    private final int mChannelNumber;
-    private final int mTransmitPreambleCodeIndex;
-    private final int mReceivePreambleCodeIndex;
-    private final int mStsPhyPacketType;
-    private final PersistableBundle mSpecificationParameters;
-
-    private RangingParams(boolean isInitiator, boolean isController,
-            @NonNull Duration samplingPeriod, @NonNull UwbAddress localDeviceAddress,
-            @NonNull List<UwbAddress> remoteDeviceAddresses, int channelNumber,
-            int transmitPreambleCodeIndex, int receivePreambleCodeIndex,
-            @StsPhyPacketType int stsPhyPacketType,
-            @NonNull PersistableBundle specificationParameters) {
-        mIsInitiator = isInitiator;
-        mIsController = isController;
-        mSamplePeriod = samplingPeriod;
-        mLocalDeviceAddress = localDeviceAddress;
-        mRemoteDeviceAddresses = remoteDeviceAddresses;
-        mChannelNumber = channelNumber;
-        mTransmitPreambleCodeIndex = transmitPreambleCodeIndex;
-        mReceivePreambleCodeIndex = receivePreambleCodeIndex;
-        mStsPhyPacketType = stsPhyPacketType;
-        mSpecificationParameters = specificationParameters;
-    }
-
-    /**
-     * Get if the local device is the initiator
-     *
-     * @return true if the device is the initiator
-     */
-    public boolean isInitiator() {
-        return mIsInitiator;
-    }
-
-    /**
-     * Get if the local device is the controller
-     *
-     * @return true if the device is the controller
-     */
-    public boolean isController() {
-        return mIsController;
-    }
-
-    /**
-     * The desired amount of time between two adjacent samples of measurement
-     *
-     * @return the ranging sample period
-     */
-    @NonNull
-    public Duration getSamplingPeriod() {
-        return mSamplePeriod;
-    }
-
-    /**
-     * Local device's {@link UwbAddress}
-     *
-     * <p>Simultaneous {@link RangingSession}s on the same device can have different results for
-     * {@link #getLocalDeviceAddress()}.
-     *
-     * @return the local device's {@link UwbAddress}
-     */
-    @NonNull
-    public UwbAddress getLocalDeviceAddress() {
-        return mLocalDeviceAddress;
-    }
-
-    /**
-     * Gets a list of all remote device's {@link UwbAddress}
-     *
-     * @return a {@link List} of {@link UwbAddress} representing the remote devices
-     */
-    @NonNull
-    public List<UwbAddress> getRemoteDeviceAddresses() {
-        return mRemoteDeviceAddresses;
-    }
-
-    /**
-     * Channel number used between this device pair as defined by 802.15.4z
-     *
-     * Range: -1, 0-15
-     *
-     * @return the channel to use
-     */
-    public int getChannelNumber() {
-        return mChannelNumber;
-    }
-
-    /**
-     * Preamble index used between this device pair as defined by 802.15.4z
-     *
-     * Range: 0, 0-32
-     *
-     * @return the preamble index to use for transmitting
-     */
-    public int getTxPreambleIndex() {
-        return mTransmitPreambleCodeIndex;
-    }
-
-    /**
-     * preamble index used between this device pair as defined by 802.15.4z
-     *
-     * Range: 0, 13-16, 21-32
-     *
-     * @return the preamble index to use for receiving
-     */
-    public int getRxPreambleIndex() {
-        return mReceivePreambleCodeIndex;
-    }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            STS_PHY_PACKET_TYPE_SP0,
-            STS_PHY_PACKET_TYPE_SP1,
-            STS_PHY_PACKET_TYPE_SP2,
-            STS_PHY_PACKET_TYPE_SP3})
-    public @interface StsPhyPacketType {}
-
-    /**
-     * PHY packet type SP0 when STS is used as defined by 802.15.4z
-     */
-    public static final int STS_PHY_PACKET_TYPE_SP0 = 0;
-
-    /**
-     * PHY packet type SP1 when STS is used as defined by 802.15.4z
-     */
-    public static final int STS_PHY_PACKET_TYPE_SP1 = 1;
-
-    /**
-     * PHY packet type SP2 when STS is used as defined by 802.15.4z
-     */
-    public static final int STS_PHY_PACKET_TYPE_SP2 = 2;
-
-    /**
-     * PHY packet type SP3 when STS is used as defined by 802.15.4z
-     */
-    public static final int STS_PHY_PACKET_TYPE_SP3 = 3;
-
-    /**
-     * Get the type of PHY packet when STS is used as defined by 802.15.4z
-     *
-     * @return the {@link StsPhyPacketType} to use
-     */
-    @StsPhyPacketType
-    public int getStsPhyPacketType() {
-        return mStsPhyPacketType;
-    }
-
-    /**
-     * Parameters for a specific UWB protocol constructed using a support library.
-     *
-     * <p>Android reserves the '^android.*' namespace
-     *
-     * @return a {@link PersistableBundle} copy of protocol specific parameters
-     */
-    public @Nullable PersistableBundle getSpecificationParameters() {
-        return new PersistableBundle(mSpecificationParameters);
-    }
-
-    /**
-     * @hide
-     */
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (obj instanceof RangingParams) {
-            RangingParams other = (RangingParams) obj;
-
-            return mIsInitiator == other.mIsInitiator
-                    && mIsController == other.mIsController
-                    && mSamplePeriod.equals(other.mSamplePeriod)
-                    && mLocalDeviceAddress.equals(other.mLocalDeviceAddress)
-                    && mRemoteDeviceAddresses.equals(other.mRemoteDeviceAddresses)
-                    && mChannelNumber == other.mChannelNumber
-                    && mTransmitPreambleCodeIndex == other.mTransmitPreambleCodeIndex
-                    && mReceivePreambleCodeIndex == other.mReceivePreambleCodeIndex
-                    && mStsPhyPacketType == other.mStsPhyPacketType
-                    && mSpecificationParameters.size() == other.mSpecificationParameters.size()
-                    && mSpecificationParameters.kindofEquals(other.mSpecificationParameters);
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIsInitiator, mIsController, mSamplePeriod, mLocalDeviceAddress,
-                mRemoteDeviceAddresses, mChannelNumber, mTransmitPreambleCodeIndex,
-                mReceivePreambleCodeIndex, mStsPhyPacketType, mSpecificationParameters);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mIsInitiator);
-        dest.writeBoolean(mIsController);
-        dest.writeLong(mSamplePeriod.getSeconds());
-        dest.writeInt(mSamplePeriod.getNano());
-        dest.writeParcelable(mLocalDeviceAddress, flags);
-
-        UwbAddress[] remoteAddresses = new UwbAddress[mRemoteDeviceAddresses.size()];
-        mRemoteDeviceAddresses.toArray(remoteAddresses);
-        dest.writeParcelableArray(remoteAddresses, flags);
-
-        dest.writeInt(mChannelNumber);
-        dest.writeInt(mTransmitPreambleCodeIndex);
-        dest.writeInt(mReceivePreambleCodeIndex);
-        dest.writeInt(mStsPhyPacketType);
-        dest.writePersistableBundle(mSpecificationParameters);
-    }
-
-    public static final @android.annotation.NonNull Creator<RangingParams> CREATOR =
-            new Creator<RangingParams>() {
-                @Override
-                public RangingParams createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-                    builder.setIsInitiator(in.readBoolean());
-                    builder.setIsController(in.readBoolean());
-                    builder.setSamplePeriod(Duration.ofSeconds(in.readLong(), in.readInt()));
-                    builder.setLocalDeviceAddress(
-                            in.readParcelable(UwbAddress.class.getClassLoader()));
-
-                    UwbAddress[] remoteAddresses =
-                            in.readParcelableArray(null, UwbAddress.class);
-                    for (UwbAddress remoteAddress : remoteAddresses) {
-                        builder.addRemoteDeviceAddress(remoteAddress);
-                    }
-
-                    builder.setChannelNumber(in.readInt());
-                    builder.setTransmitPreambleCodeIndex(in.readInt());
-                    builder.setReceivePreambleCodeIndex(in.readInt());
-                    builder.setStsPhPacketType(in.readInt());
-                    builder.setSpecificationParameters(in.readPersistableBundle());
-
-                    return builder.build();
-                }
-
-                @Override
-                public RangingParams[] newArray(int size) {
-                    return new RangingParams[size];
-                }
-    };
-
-    /**
-     * Builder class for {@link RangingParams}.
-     */
-    public static final class Builder {
-        private boolean mIsInitiator = false;
-        private boolean mIsController = false;
-        private Duration mSamplePeriod = null;
-        private UwbAddress mLocalDeviceAddress = null;
-        private List<UwbAddress> mRemoteDeviceAddresses = new ArrayList<>();
-        private int mChannelNumber = 0;
-        private int mTransmitPreambleCodeIndex = 0;
-        private int mReceivePreambleCodeIndex = 0;
-        private int mStsPhyPacketType = STS_PHY_PACKET_TYPE_SP0;
-        private PersistableBundle mSpecificationParameters = new PersistableBundle();
-
-        /**
-         * Set whether the device is the initiator or responder as defined by IEEE 802.15.4z
-         *
-         * @param isInitiator whether the device is the initiator (true) or responder (false)
-         */
-        public Builder setIsInitiator(boolean isInitiator) {
-            mIsInitiator = isInitiator;
-            return this;
-        }
-
-        /**
-         * Set whether the local device is the controller or controlee as defined by IEEE 802.15.4z
-         *
-         * @param isController whether the device is the controller (true) or controlee (false)
-         */
-        public Builder setIsController(boolean isController) {
-            mIsController = isController;
-            return this;
-        }
-
-        /**
-         * Set the time between ranging samples
-         *
-         * @param samplePeriod the time between ranging samples
-         */
-        public Builder setSamplePeriod(@NonNull Duration samplePeriod) {
-            mSamplePeriod = samplePeriod;
-            return this;
-        }
-
-        /**
-         * Set the local device address
-         *
-         * @param localDeviceAddress the local device's address for the {@link RangingSession}
-         */
-        public Builder setLocalDeviceAddress(@NonNull UwbAddress localDeviceAddress) {
-            mLocalDeviceAddress = localDeviceAddress;
-            return this;
-        }
-
-        /**
-         * Add a remote device's address to the ranging session
-         *
-         * @param remoteDeviceAddress a remote device's address for the {@link RangingSession}
-         * @throws IllegalArgumentException if {@code remoteDeviceAddress} is already present.
-         */
-        public Builder addRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) {
-            if (mRemoteDeviceAddresses.contains(remoteDeviceAddress)) {
-                throw new IllegalArgumentException(
-                        "Remote device address already added: " + remoteDeviceAddress.toString());
-            }
-            mRemoteDeviceAddresses.add(remoteDeviceAddress);
-            return this;
-        }
-
-        /**
-         * Set the IEEE 802.15.4z channel to use for the {@link RangingSession}
-         * <p>Valid values are in the range [-1, 15]
-         *
-         * @param channelNumber the channel to use for the {@link RangingSession}
-         * @throws IllegalArgumentException if {@code channelNumber} is invalid.
-         */
-        public Builder setChannelNumber(int channelNumber) {
-            if (channelNumber < -1 || channelNumber > 15) {
-                throw new IllegalArgumentException("Invalid channel number");
-            }
-            mChannelNumber = channelNumber;
-            return this;
-        }
-
-        private static final Set<Integer> VALID_TX_PREAMBLE_CODES = new HashSet<Integer>(
-                Arrays.asList(0, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32));
-
-        /**
-         * Set the IEEE 802.15.4z preamble code index to use when transmitting
-         *
-         * <p>Valid values are in the ranges: [0], [13-16], [21-32]
-         *
-         * @param transmitPreambleCodeIndex preamble code index to use for transmitting
-         * @throws IllegalArgumentException if {@code transmitPreambleCodeIndex} is invalid.
-         */
-        public Builder setTransmitPreambleCodeIndex(int transmitPreambleCodeIndex) {
-            if (!VALID_TX_PREAMBLE_CODES.contains(transmitPreambleCodeIndex)) {
-                throw new IllegalArgumentException(
-                        "Invalid transmit preamble: " + transmitPreambleCodeIndex);
-            }
-            mTransmitPreambleCodeIndex = transmitPreambleCodeIndex;
-            return this;
-        }
-
-        private static final Set<Integer> VALID_RX_PREAMBLE_CODES = new HashSet<Integer>(
-                Arrays.asList(0, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32));
-
-        /**
-         * Set the IEEE 802.15.4z preamble code index to use when receiving
-         *
-         * Valid values are in the ranges: [0], [16-32]
-         *
-         * @param receivePreambleCodeIndex preamble code index to use for receiving
-         * @throws IllegalArgumentException if {@code receivePreambleCodeIndex} is invalid.
-         */
-        public Builder setReceivePreambleCodeIndex(int receivePreambleCodeIndex) {
-            if (!VALID_RX_PREAMBLE_CODES.contains(receivePreambleCodeIndex)) {
-                throw new IllegalArgumentException(
-                        "Invalid receive preamble: " + receivePreambleCodeIndex);
-            }
-            mReceivePreambleCodeIndex = receivePreambleCodeIndex;
-            return this;
-        }
-
-        /**
-         * Set the IEEE 802.15.4z PHY packet type when STS is used
-         *
-         * @param stsPhyPacketType PHY packet type when STS is used
-         * @throws IllegalArgumentException if {@code stsPhyPacketType} is invalid.
-         */
-        public Builder setStsPhPacketType(@StsPhyPacketType int stsPhyPacketType) {
-            if (stsPhyPacketType != STS_PHY_PACKET_TYPE_SP0
-                    && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP1
-                    && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP2
-                    && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP3) {
-                throw new IllegalArgumentException("unknown StsPhyPacketType: " + stsPhyPacketType);
-            }
-
-            mStsPhyPacketType = stsPhyPacketType;
-            return this;
-        }
-
-        /**
-         * Set the specification parameters
-         *
-         * <p>Creates a copy of the parameters
-         *
-         * @param parameters specification parameters built from support library
-         */
-        public Builder setSpecificationParameters(@NonNull PersistableBundle parameters) {
-            mSpecificationParameters = new PersistableBundle(parameters);
-            return this;
-        }
-
-        /**
-         * Build the {@link RangingParams} object.
-         *
-         * @throws IllegalStateException if required parameters are missing
-         */
-        public RangingParams build() {
-            if (mSamplePeriod == null) {
-                throw new IllegalStateException("No sample period provided");
-            }
-
-            if (mLocalDeviceAddress == null) {
-                throw new IllegalStateException("Local device address not provided");
-            }
-
-            if (mRemoteDeviceAddresses.size() == 0) {
-                throw new IllegalStateException("No remote device address(es) provided");
-            }
-
-            return new RangingParams(mIsInitiator, mIsController, mSamplePeriod,
-                    mLocalDeviceAddress, mRemoteDeviceAddresses, mChannelNumber,
-                    mTransmitPreambleCodeIndex, mReceivePreambleCodeIndex, mStsPhyPacketType,
-                    mSpecificationParameters);
-        }
-    }
-}
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index f4033fe..8639269 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -30,7 +30,7 @@
  * {@link RangingSession}.
  *
  * <p>To get an instance of {@link RangingSession}, first use
- * {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} to request to open a
+ * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a
  * session. Once the session is opened, a {@link RangingSession} object is provided through
  * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a
  * session fails, the failure is reported through {@link RangingSession.Callback#onClosed(int)} with
@@ -44,7 +44,7 @@
      */
     public interface Callback {
         /**
-         * Invoked when {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)}
+         * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
          * is successful
          *
          * @param session the newly opened {@link RangingSession}
@@ -77,7 +77,7 @@
 
         /**
          * Indicates that the session failed to open due to erroneous parameters passed
-         * to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)}
+         * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
          */
         int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2;
 
@@ -137,8 +137,8 @@
      * will still be invoked.
      *
      * <p>{@link Callback#onClosed(int)} will be invoked using the same callback
-     * object given to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} when
-     * the {@link RangingSession} was opened. The callback will be invoked after each call to
+     * object given to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
+     * when the {@link RangingSession} was opened. The callback will be invoked after each call to
      * {@link #close()}, even if the {@link RangingSession} is already closed.
      */
     @Override
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 6bf7d6f..2f1e2de 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -279,7 +279,10 @@
      * <p>An open {@link RangingSession} will be automatically closed if client application process
      * dies.
      *
-     * @param params {@link RangingParams} used to initialize this {@link RangingSession}
+     * <p>A UWB support library must be used in order to construct the {@code parameter}
+     * {@link PersistableBundle}.
+     *
+     * @param parameters the parameters that define the ranging session
      * @param executor {@link Executor} to run callbacks
      * @param callbacks {@link RangingSession.Callback} to associate with the
      *                  {@link RangingSession} that is being opened.
@@ -290,8 +293,9 @@
      *         {@link RangingSession.Callback#onOpenSuccess}.
      */
     @NonNull
-    public AutoCloseable openRangingSession(@NonNull RangingParams params,
-            @NonNull Executor executor, @NonNull RangingSession.Callback callbacks) {
+    public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
+            @NonNull Executor executor,
+            @NonNull RangingSession.Callback callbacks) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index bc3e35c..6cc3cd3 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -78,6 +78,12 @@
     public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
 
     /**
+     * Display area for hiding display cutout feature
+     * @hide
+     */
+    public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
+
+    /**
      * The last boundary of display area for system features
      */
     public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index f8aac42..3874de3 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -38,8 +38,12 @@
      *
      * It is good practice to clear the binder calling identity prior to calling this, in case the
      * caller is ever in the same process as the callee.
+     *
+     * The binder object being passed is used by the server to keep track of client death, in order
+     * to clean-up whenever that happens.
      */
-    ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity);
+    ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity,
+                                            IBinder client);
 
     /**
      * Creates a new session.
@@ -54,7 +58,11 @@
      *
      * It is good practice to clear the binder calling identity prior to calling this, in case the
      * caller is ever in the same process as the callee.
+     *
+     * The binder object being passed is used by the server to keep track of client death, in order
+     * to clean-up whenever that happens.
      */
     ISoundTriggerSession attachAsMiddleman(in Identity middlemanIdentity,
-                                           in Identity originatorIdentity);
+                                           in Identity originatorIdentity,
+                                           IBinder client);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 81c61aa..db25514 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -216,7 +216,11 @@
      * Caller must provide an identity, used for permission tracking purposes.
      * The uid/pid elements of the identity will be ignored by the server and replaced with the ones
      * provided by binder.
+     *
+     * The client argument is any binder owned by the client, used for tracking is death and
+     * cleaning up in this event.
      */
     IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
-            in Identity originatorIdentity);
+            in Identity originatorIdentity,
+            IBinder client);
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 137430b..bb7e2af 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -348,6 +348,57 @@
     }
 
     /**
+     * A helper method to translate interaction type to CUJ name.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the name of the interaction type
+     */
+    public static String getNameOfInteraction(int interactionType) {
+        // There is an offset amount of 1 between cujType and interactionType.
+        return getNameOfCuj(interactionType - 1);
+    }
+
+    private static String getNameOfCuj(int cujType) {
+        switch (cujType) {
+            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
+                return "SHADE_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
+                return "SHADE_EXPAND_COLLAPSE_LOCK";
+            case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
+                return "SHADE_SCROLL_FLING";
+            case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
+                return "SHADE_ROW_EXPAND";
+            case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
+                return "SHADE_ROW_SWIPE";
+            case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
+                return "SHADE_QS_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
+                return "SHADE_QS_SCROLL_SWIPE";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
+                return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
+                return "LAUNCHER_APP_LAUNCH_FROM_ICON";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
+                return "LAUNCHER_APP_CLOSE_TO_HOME";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
+                return "LAUNCHER_APP_CLOSE_TO_PIP";
+            case CUJ_LAUNCHER_QUICK_SWITCH:
+                return "LAUNCHER_QUICK_SWITCH";
+            case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
+                return "NOTIFICATION_HEADS_UP_APPEAR";
+            case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
+                return "NOTIFICATION_HEADS_UP_DISAPPEAR";
+            case CUJ_NOTIFICATION_ADD:
+                return "NOTIFICATION_ADD";
+            case CUJ_NOTIFICATION_REMOVE:
+                return "NOTIFICATION_REMOVE";
+            case CUJ_NOTIFICATION_APP_START:
+                return "NOTIFICATION_APP_START";
+        }
+        return "UNKNOWN";
+    }
+
+    /**
      * A class to represent a session.
      */
     public static class Session {
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c110b26..c85b5d7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -18,9 +18,10 @@
 package com.android.internal.os;
 
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.BasicShellCommandHandler;
 import android.os.Build;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintStream;
 
 public abstract class BaseCommand {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 433a46b..3295df1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4510,4 +4510,7 @@
          activity-level letterboxing (size-compat mode). Therefore this override can control the
          maximum screen area that can be occupied by the app in the letterbox mode. -->
     <item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item>
+
+    <!-- If true, hide the display cutout with display area -->
+    <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6960fb3..fba431c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4095,4 +4095,6 @@
   <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
 
   <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
+
+  <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 </resources>
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index cfed2ce..4393e9e7 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -17,12 +17,14 @@
 package android.graphics;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.graphics.fonts.SystemFonts;
+import android.os.SharedMemory;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -35,9 +37,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
@@ -54,14 +55,33 @@
         Typeface.create(Typeface.MONOSPACE, 0)
     };
 
+    private static final int[] STYLES = {
+        Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC,
+    };
+
     @SmallTest
     @Test
-    public void testBasic() throws Exception {
-        assertTrue("basic", Typeface.DEFAULT != null);
-        assertTrue("basic", Typeface.DEFAULT_BOLD != null);
-        assertTrue("basic", Typeface.SANS_SERIF != null);
-        assertTrue("basic", Typeface.SERIF != null);
-        assertTrue("basic", Typeface.MONOSPACE != null);
+    public void testBasic() {
+        assertNotEquals("basic", 0, Typeface.DEFAULT.native_instance);
+        assertNotEquals("basic", 0, Typeface.DEFAULT_BOLD.native_instance);
+        assertNotEquals("basic", 0, Typeface.SANS_SERIF.native_instance);
+        assertNotEquals("basic", 0, Typeface.SERIF.native_instance);
+        assertNotEquals("basic", 0, Typeface.MONOSPACE.native_instance);
+        assertEquals("basic", Typeface.NORMAL, Typeface.DEFAULT.getStyle());
+        assertEquals("basic", Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+        assertEquals("basic", Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
+        assertEquals("basic", Typeface.NORMAL, Typeface.SERIF.getStyle());
+        assertEquals("basic", Typeface.NORMAL, Typeface.MONOSPACE.getStyle());
+    }
+
+    @SmallTest
+    @Test
+    public void testDefaults() {
+        for (int style : STYLES) {
+            String msg = "style = " + style;
+            assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
+            assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+        }
     }
 
     @SmallTest
@@ -178,21 +198,67 @@
     @SmallTest
     @Test
     public void testSerialize() throws Exception {
-        int size = Typeface.writeTypefaces(null, Arrays.asList(mFaces));
-        ByteBuffer buffer = ByteBuffer.allocateDirect(size);
-        Typeface.writeTypefaces(buffer, Arrays.asList(mFaces));
-        List<Typeface> copiedTypefaces = Typeface.readTypefaces(buffer);
-        assertNotNull(copiedTypefaces);
-        assertEquals(mFaces.length, copiedTypefaces.size());
-        for (int i = 0; i < mFaces.length; i++) {
-            Typeface original = mFaces[i];
-            Typeface copied = copiedTypefaces.get(i);
+        HashMap<String, Typeface> systemFontMap = new HashMap<>();
+        Typeface.initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
+                SystemFonts.getAliases());
+        SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
+        Map<String, Typeface> copiedFontMap = Typeface.deserializeFontMap(sharedMemory);
+        assertEquals(systemFontMap.size(), copiedFontMap.size());
+        for (String key : systemFontMap.keySet()) {
+            assertTrue(copiedFontMap.containsKey(key));
+            Typeface original = systemFontMap.get(key);
+            Typeface copied = copiedFontMap.get(key);
             assertEquals(original.getStyle(), copied.getStyle());
             assertEquals(original.getWeight(), copied.getWeight());
             assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
         }
     }
 
+    @SmallTest
+    @Test
+    public void testSetSystemFontMap() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Resources res = context.getResources();
+        Map<String, Typeface> fontMap = Map.of(
+                "sans-serif", Typeface.create(res.getFont(R.font.samplefont), Typeface.NORMAL),
+                "serif", Typeface.create(res.getFont(R.font.samplefont2), Typeface.NORMAL),
+                "monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
+                "sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
+                "sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
+        Typeface.setSystemFontMap(fontMap);
+
+        // Test public static final fields
+        assertEquals(fontMap.get("sans-serif").native_instance, Typeface.DEFAULT.native_instance);
+        assertNotEquals(0, Typeface.DEFAULT_BOLD.native_instance);
+        assertEquals(
+                fontMap.get("sans-serif").native_instance, Typeface.SANS_SERIF.native_instance);
+        assertEquals(fontMap.get("serif").native_instance, Typeface.SERIF.native_instance);
+        assertEquals(fontMap.get("monospace").native_instance, Typeface.MONOSPACE.native_instance);
+        assertEquals(Typeface.NORMAL, Typeface.DEFAULT.getStyle());
+        assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+        assertEquals(Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
+        assertEquals(Typeface.NORMAL, Typeface.SERIF.getStyle());
+        assertEquals(Typeface.NORMAL, Typeface.MONOSPACE.getStyle());
+
+        // Test defaults
+        assertEquals(
+                fontMap.get("sans-serif").native_instance,
+                Typeface.defaultFromStyle(Typeface.NORMAL).native_instance);
+        for (int style : STYLES) {
+            String msg = "style = " + style;
+            assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
+            assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+        }
+
+        // Test create()
+        assertEquals(
+                fontMap.get("sample").native_instance,
+                Typeface.create("sample", Typeface.NORMAL).native_instance);
+        assertEquals(
+                fontMap.get("sample-italic").native_instance,
+                Typeface.create("sample-italic", Typeface.ITALIC).native_instance);
+    }
+
     private static float measureText(Typeface typeface, String text) {
         Paint paint = new Paint();
         paint.setTypeface(typeface);
diff --git a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java b/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
deleted file mode 100644
index 8095c99..0000000
--- a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.os.PersistableBundle;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Duration;
-
-/**
- * Test of {@link RangingParams}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingParamsTest {
-
-    @Test
-    public void testParams_Build() {
-        UwbAddress local = UwbAddress.fromBytes(new byte[] {(byte) 0xA0, (byte) 0x57});
-        UwbAddress remote = UwbAddress.fromBytes(new byte[] {(byte) 0x4D, (byte) 0x8C});
-        int channel = 9;
-        int rxPreamble = 16;
-        int txPreamble = 21;
-        boolean isController = true;
-        boolean isInitiator = false;
-        @RangingParams.StsPhyPacketType int stsPhyType = RangingParams.STS_PHY_PACKET_TYPE_SP2;
-        Duration samplePeriod = Duration.ofSeconds(1, 234);
-        PersistableBundle specParams = new PersistableBundle();
-        specParams.putString("protocol", "some_protocol");
-
-        RangingParams params = new RangingParams.Builder()
-                .setChannelNumber(channel)
-                .setReceivePreambleCodeIndex(rxPreamble)
-                .setTransmitPreambleCodeIndex(txPreamble)
-                .setLocalDeviceAddress(local)
-                .addRemoteDeviceAddress(remote)
-                .setIsController(isController)
-                .setIsInitiator(isInitiator)
-                .setSamplePeriod(samplePeriod)
-                .setStsPhPacketType(stsPhyType)
-                .setSpecificationParameters(specParams)
-                .build();
-
-        assertEquals(params.getLocalDeviceAddress(), local);
-        assertEquals(params.getRemoteDeviceAddresses().size(), 1);
-        assertEquals(params.getRemoteDeviceAddresses().get(0), remote);
-        assertEquals(params.getChannelNumber(), channel);
-        assertEquals(params.isController(), isController);
-        assertEquals(params.isInitiator(), isInitiator);
-        assertEquals(params.getRxPreambleIndex(), rxPreamble);
-        assertEquals(params.getTxPreambleIndex(), txPreamble);
-        assertEquals(params.getStsPhyPacketType(), stsPhyType);
-        assertEquals(params.getSamplingPeriod(), samplePeriod);
-        assertTrue(params.getSpecificationParameters().kindofEquals(specParams));
-    }
-
-    @Test
-    public void testParcel() {
-        Parcel parcel = Parcel.obtain();
-        RangingParams params = new RangingParams.Builder()
-                .setChannelNumber(9)
-                .setReceivePreambleCodeIndex(16)
-                .setTransmitPreambleCodeIndex(21)
-                .setLocalDeviceAddress(UwbTestUtils.getUwbAddress(false))
-                .addRemoteDeviceAddress(UwbTestUtils.getUwbAddress(true))
-                .setIsController(false)
-                .setIsInitiator(true)
-                .setSamplePeriod(Duration.ofSeconds(2))
-                .setStsPhPacketType(RangingParams.STS_PHY_PACKET_TYPE_SP1)
-                .build();
-        params.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        RangingParams fromParcel = RangingParams.CREATOR.createFromParcel(parcel);
-        assertEquals(params, fromParcel);
-    }
-}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index c58e5f3..d44cb9c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,8 +34,12 @@
 import android.graphics.fonts.SystemFonts;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+import android.os.Trace;
 import android.provider.FontRequest;
 import android.provider.FontsContract;
+import android.system.ErrnoException;
+import android.system.OsConstants;
 import android.text.FontConfig;
 import android.util.Base64;
 import android.util.LongSparseArray;
@@ -50,6 +54,7 @@
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -57,6 +62,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -79,19 +85,19 @@
             Typeface.class.getClassLoader(), nativeGetReleaseFunc());
 
     /** The default NORMAL typeface object */
-    public static final Typeface DEFAULT;
+    public static final Typeface DEFAULT = new Typeface();
     /**
      * The default BOLD typeface object. Note: this may be not actually be
      * bold, depending on what fonts are installed. Call getStyle() to know
      * for sure.
      */
-    public static final Typeface DEFAULT_BOLD;
+    public static final Typeface DEFAULT_BOLD = new Typeface();
     /** The NORMAL style of the default sans serif typeface. */
-    public static final Typeface SANS_SERIF;
+    public static final Typeface SANS_SERIF = new Typeface();
     /** The NORMAL style of the default serif typeface. */
-    public static final Typeface SERIF;
+    public static final Typeface SERIF = new Typeface();
     /** The NORMAL style of the default monospace typeface. */
-    public static final Typeface MONOSPACE;
+    public static final Typeface MONOSPACE = new Typeface();
 
     /**
      * The default {@link Typeface}s for different text styles.
@@ -133,7 +139,7 @@
      * Use public API {@link #create(String, int)} to get the typeface for given familyName.
      */
     @UnsupportedAppUsage(trackingBug = 123769347)
-    static final Map<String, Typeface> sSystemFontMap;
+    static final Map<String, Typeface> sSystemFontMap = new HashMap<>();
 
     // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
     /**
@@ -150,6 +156,9 @@
     @UnsupportedAppUsage
     public long native_instance;
 
+    // This destructs native_instance.
+    private Runnable mCleaner;
+
     /** @hide */
     @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
     @Retention(RetentionPolicy.SOURCE)
@@ -1086,19 +1095,41 @@
         return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
     }
 
+    /**
+     * Creates a fake Typeface object that are later modified to point to another Typeface object
+     * using {@link #reset(Typeface, int)}.
+     *
+     * <p>This constructor is only for filling 'static final' Typeface instances in Zygote process.
+     */
+    private Typeface() {
+    }
+
     // don't allow clients to call this directly
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private Typeface(long ni) {
+        init(ni);
+    }
+
+    private void init(long ni) {
         if (ni == 0) {
             throw new RuntimeException("native typeface cannot be made");
         }
-
+        if (mCleaner != null) {
+            mCleaner.run();
+        }
         native_instance = ni;
-        sRegistry.registerNativeAllocation(this, native_instance);
+        mCleaner = sRegistry.registerNativeAllocation(this, native_instance);
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
     }
 
+    private void reset(Typeface typeface, int style) {
+        // Just create a new native instance without looking into the cache, because we are going to
+        // claim the ownership.
+        long ni = nativeCreateFromTypeface(typeface.native_instance, style);
+        init(ni);
+    }
+
     private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
         Typeface tf = sSystemFontMap.get(familyName);
         return tf == null ? Typeface.DEFAULT : tf;
@@ -1137,23 +1168,105 @@
         }
     }
 
-    static {
-        final HashMap<String, Typeface> systemFontMap = new HashMap<>();
-        initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
-                SystemFonts.getAliases());
-        sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
+    /** @hide */
+    public static SharedMemory serializeFontMap(Map<String, Typeface> fontMap)
+            throws IOException, ErrnoException {
+        long[] nativePtrs = new long[fontMap.size()];
+        // The name table will not be large, so let's create a byte array in memory.
+        ByteArrayOutputStream namesBytes = new ByteArrayOutputStream();
+        int i = 0;
+        for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) {
+            nativePtrs[i++] = entry.getValue().native_instance;
+            writeString(namesBytes, entry.getKey());
+        }
+        int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs);
+        // int (typefacesBytesCount), typefaces, namesBytes
+        SharedMemory sharedMemory = SharedMemory.create(
+                "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size());
+        ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
+        try {
+            writableBuffer.putInt(typefacesBytesCount);
+            int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs);
+            if (writtenBytesCount != typefacesBytesCount) {
+                throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
+                        writtenBytesCount, typefacesBytesCount));
+            }
+            writableBuffer.position(writableBuffer.position() + writtenBytesCount);
+            writableBuffer.put(namesBytes.toByteArray());
+        } finally {
+            SharedMemory.unmap(writableBuffer);
+        }
+        sharedMemory.setProtect(OsConstants.PROT_READ);
+        return sharedMemory;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static Map<String, Typeface> deserializeFontMap(SharedMemory sharedMemory)
+            throws IOException, ErrnoException {
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "deserializeFontMap");
+        try {
+            // TODO: unmap buffer when all Typefaces are gone.
+            // Currently deserializeFontMap() should be called at most once per process, so we don't
+            // need to unmap this buffer.
+            ByteBuffer buffer = sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
+            Map<String, Typeface> fontMap = new HashMap<>();
+            int typefacesBytesCount = buffer.getInt();
+            long[] nativePtrs = nativeReadTypefaces(buffer.slice());
+            if (nativePtrs == null) {
+                throw new IOException("Could not read typefaces");
+            }
+            buffer.position(buffer.position() + typefacesBytesCount);
+            for (long nativePtr : nativePtrs) {
+                String name = readString(buffer);
+                fontMap.put(name, new Typeface(nativePtr));
+            }
+            return fontMap;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        }
+    }
+
+    private static String readString(ByteBuffer buffer) {
+        int length = buffer.getInt();
+        byte[] bytes = new byte[length];
+        buffer.get(bytes);
+        return new String(bytes);
+    }
+
+    private static void writeString(ByteArrayOutputStream bos, String value) throws IOException {
+        byte[] bytes = value.getBytes();
+        writeInt(bos, bytes.length);
+        bos.write(bytes);
+    }
+
+    private static void writeInt(ByteArrayOutputStream bos, int value) {
+        // Write in the big endian order.
+        bos.write((value >> 24) & 0xFF);
+        bos.write((value >> 16) & 0xFF);
+        bos.write((value >> 8) & 0xFF);
+        bos.write(value & 0xFF);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static void setSystemFontMap(Map<String, Typeface> systemFontMap) {
+        sSystemFontMap.clear();
+        sSystemFontMap.putAll(systemFontMap);
 
         // We can't assume DEFAULT_FAMILY available on Roboletric.
         if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
             setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
         }
 
-        // Set up defaults and typefaces exposed in public API
-        DEFAULT         = create((String) null, 0);
-        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
-        SANS_SERIF      = create("sans-serif", 0);
-        SERIF           = create("serif", 0);
-        MONOSPACE       = create("monospace", 0);
+        // Set up defaults and typefaces exposed in public API.
+        // We cannot use getSystemDefaultTypeface() here to initialize DEFAULT, because it returns
+        // DEFAULT.
+        DEFAULT.reset(sDefaultTypeface, Typeface.NORMAL);
+        DEFAULT_BOLD.reset(sDefaultTypeface, Typeface.BOLD);
+        SANS_SERIF.reset(getSystemDefaultTypeface("sans-serif"), Typeface.NORMAL);
+        SERIF.reset(getSystemDefaultTypeface("serif"), Typeface.NORMAL);
+        MONOSPACE.reset(getSystemDefaultTypeface("monospace"), Typeface.NORMAL);
 
         sDefaults = new Typeface[] {
             DEFAULT,
@@ -1173,6 +1286,13 @@
         }
     }
 
+    static {
+        final HashMap<String, Typeface> systemFontMap = new HashMap<>();
+        initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
+                SystemFonts.getAliases());
+        setSystemFontMap(systemFontMap);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -1210,36 +1330,6 @@
         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
     }
 
-    /**
-     * Writes Typeface instances to the ByteBuffer and returns the number of bytes written.
-     *
-     * <p>If {@code buffer} is null, this method returns the number of bytes required to serialize
-     * the typefaces, without writing anything.
-     * @hide
-     */
-    public static int writeTypefaces(
-            @Nullable ByteBuffer buffer, @NonNull List<Typeface> typefaces) {
-        long[] nativePtrs = new long[typefaces.size()];
-        for (int i = 0; i < nativePtrs.length; i++) {
-            nativePtrs[i] = typefaces.get(i).native_instance;
-        }
-        return nativeWriteTypefaces(buffer, nativePtrs);
-    }
-
-    /**
-     * Reads serialized Typeface instances from the ByteBuffer. Returns null on errors.
-     * @hide
-     */
-    public static @Nullable List<Typeface> readTypefaces(@NonNull ByteBuffer buffer) {
-        long[] nativePtrs = nativeReadTypefaces(buffer);
-        if (nativePtrs == null) return null;
-        List<Typeface> typefaces = new ArrayList<>(nativePtrs.length);
-        for (long nativePtr : nativePtrs) {
-            typefaces.add(new Typeface(nativePtr));
-        }
-        return typefaces;
-    }
-
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native long nativeCreateFromTypefaceWithExactStyle(
             long native_instance, int weight, boolean italic);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 71e6559..d1b4464 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -23,6 +23,7 @@
 import android.security.keymaster.ExportResult;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterDefs;
+import android.sysprop.Keystore2Properties;
 
 import java.io.IOException;
 import java.security.KeyFactory;
@@ -111,6 +112,26 @@
         putSecretKeyFactoryImpl("HmacSHA512");
     }
 
+    private static boolean sKeystore2Enabled;
+
+    /**
+     * This function indicates whether or not Keystore 2.0 is enabled. Some parts of the
+     * Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However,
+     * the platform property that indicates that Keystore 2.0 is enabled is not readable
+     * by applications. So we set this value when {@code install()} is called because it
+     * is called by zygote, which can access Keystore2Properties.
+     *
+     * This function can be removed once the transition to Keystore 2.0 is complete.
+     * b/171305684
+     *
+     * @return true if Keystore 2.0 is enabled.
+     * @hide
+     */
+    public static boolean isKeystore2Enabled() {
+        return sKeystore2Enabled;
+    }
+
+
     /**
      * Installs a new instance of this provider (and the
      * {@link AndroidKeyStoreBCWorkaroundProvider}).
@@ -138,6 +159,11 @@
             // priority.
             Security.addProvider(workaroundProvider);
         }
+
+        // {@code install()} is run by zygote when this property is still accessible. We store its
+        // value so that the Keystore SPI can act accordingly without having to access an internal
+        // property.
+        sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false);
     }
 
     private void putSecretKeyFactoryImpl(String algorithm) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 9707260..3694d63 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -211,7 +211,11 @@
                 userAuthenticationValidWhileOnBody,
                 trustedUserPresenceRequired,
                 invalidatedByBiometricEnrollment,
-                userConfirmationRequired);
+                userConfirmationRequired,
+                // Keystore 1.0 does not tell us the exact security level of the key
+                // so we report an unknown but secure security level.
+                insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE
+                        : KeyProperties.SECURITY_LEVEL_SOFTWARE);
     }
 
     private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 688c4a7..e9aac7d 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -27,7 +27,6 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Build;
 import android.security.GateKeeper;
-import android.security.KeyStore;
 import android.text.TextUtils;
 
 import java.math.BigInteger;
@@ -246,7 +245,7 @@
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
 
     private final String mKeystoreAlias;
-    private final int mUid;
+    private final int mNamespace;
     private final int mKeySize;
     private final AlgorithmParameterSpec mSpec;
     private final X500Principal mCertificateSubject;
@@ -286,7 +285,7 @@
      */
     public KeyGenParameterSpec(
             String keyStoreAlias,
-            int uid,
+            int namespace,
             int keySize,
             AlgorithmParameterSpec spec,
             X500Principal certificateSubject,
@@ -337,7 +336,7 @@
         }
 
         mKeystoreAlias = keyStoreAlias;
-        mUid = uid;
+        mNamespace = namespace;
         mKeySize = keySize;
         mSpec = spec;
         mCertificateSubject = certificateSubject;
@@ -382,11 +381,43 @@
      * Returns the UID which will own the key. {@code -1} is an alias for the UID of the current
      * process.
      *
+     * @deprecated See deprecation message on {@link KeyGenParameterSpec.Builder#setUid(int)}.
+     *             Known namespaces will be translated to their legacy UIDs. Unknown
+     *             Namespaces will yield {@link IllegalStateException}.
+     *
      * @hide
      */
     @UnsupportedAppUsage
+    @Deprecated
     public int getUid() {
-        return mUid;
+        if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
+            // If Keystore2 has not been enabled we have to behave as if mNamespace is actually
+            // a UID, because we are still being used with the old Keystore SPI.
+            // TODO This if statement and body can be removed when the Keystore 2 migration is
+            //      complete. b/171563717
+            return mNamespace;
+        }
+        try {
+            return KeyProperties.namespaceToLegacyUid(mNamespace);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalStateException("getUid called on KeyGenParameterSpec with non legacy"
+                    + " keystore namespace.", e);
+        }
+    }
+
+    /**
+     * Returns the target namespace for the key.
+     * See {@link KeyGenParameterSpec.Builder#setNamespace(int)}.
+     *
+     * @return The numeric namespace as configured in the keystore2_key_contexts files of Android's
+     *         SEPolicy.
+     *         TODO b/171806779 link to public Keystore 2.0 documentation.
+     *              See bug for more details for now.
+     * @hide
+     */
+    @SystemApi
+    public int getNamespace() {
+        return mNamespace;
     }
 
     /**
@@ -767,7 +798,7 @@
         private final String mKeystoreAlias;
         private @KeyProperties.PurposeEnum int mPurposes;
 
-        private int mUid = KeyStore.UID_SELF;
+        private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
         private int mKeySize = -1;
         private AlgorithmParameterSpec mSpec;
         private X500Principal mCertificateSubject;
@@ -830,7 +861,7 @@
          */
         public Builder(@NonNull KeyGenParameterSpec sourceSpec) {
             this(sourceSpec.getKeystoreAlias(), sourceSpec.getPurposes());
-            mUid = sourceSpec.getUid();
+            mNamespace = sourceSpec.getNamespace();
             mKeySize = sourceSpec.getKeySize();
             mSpec = sourceSpec.getAlgorithmParameterSpec();
             mCertificateSubject = sourceSpec.getCertificateSubject();
@@ -873,12 +904,51 @@
          *
          * @param uid UID or {@code -1} for the UID of the current process.
          *
+         * @deprecated Setting the UID of the target namespace is based on a hardcoded
+         * hack in the Keystore service. This is no longer supported with Keystore 2.0/Android S.
+         * Instead, dedicated non UID based namespaces can be configured in SEPolicy using
+         * the keystore2_key_contexts files. The functionality of this method will be supported
+         * by mapping knows special UIDs, such as WIFI, to the newly configured SELinux based
+         * namespaces. Unknown UIDs will yield {@link IllegalArgumentException}.
+         *
          * @hide
          */
         @SystemApi
         @NonNull
+        @Deprecated
         public Builder setUid(int uid) {
-            mUid = uid;
+            if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
+                // If Keystore2 has not been enabled we have to behave as if mNamespace is actually
+                // a UID, because we are still being used with the old Keystore SPI.
+                // TODO This if statement and body can be removed when the Keystore 2 migration is
+                //      complete. b/171563717
+                mNamespace = uid;
+                return this;
+            }
+            mNamespace = KeyProperties.legacyUidToNamespace(uid);
+            return this;
+        }
+
+        /**
+         * Set the designated SELinux namespace that the key shall live in. The caller must
+         * have sufficient permissions to install a key in the given namespace. Namespaces
+         * can be created using SEPolicy. The keystore2_key_contexts files map numeric
+         * namespaces to SELinux labels, and SEPolicy can be used to grant access to these
+         * namespaces to the desired target context. This is the preferred way to share
+         * keys between system and vendor components, e.g., WIFI settings and WPA supplicant.
+         *
+         * @param namespace Numeric SELinux namespace as configured in keystore2_key_contexts
+         *                  of Android's SEPolicy.
+         *                  TODO b/171806779 link to public Keystore 2.0 documentation.
+         *                       See bug for more details for now.
+         * @return this Builder object.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setNamespace(int namespace) {
+            mNamespace = namespace;
             return this;
         }
 
@@ -1489,7 +1559,7 @@
         public KeyGenParameterSpec build() {
             return new KeyGenParameterSpec(
                     mKeystoreAlias,
-                    mUid,
+                    mNamespace,
                     mKeySize,
                     mSpec,
                     mCertificateSubject,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index d891a25..7158d0c 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -84,6 +84,7 @@
     private final boolean mTrustedUserPresenceRequired;
     private final boolean mInvalidatedByBiometricEnrollment;
     private final boolean mUserConfirmationRequired;
+    private final @KeyProperties.SecurityLevelEnum int mSecurityLevel;
 
     /**
      * @hide
@@ -107,7 +108,8 @@
             boolean userAuthenticationValidWhileOnBody,
             boolean trustedUserPresenceRequired,
             boolean invalidatedByBiometricEnrollment,
-            boolean userConfirmationRequired) {
+            boolean userConfirmationRequired,
+            @KeyProperties.SecurityLevelEnum int securityLevel) {
         mKeystoreAlias = keystoreKeyAlias;
         mInsideSecureHardware = insideSecureHardware;
         mOrigin = origin;
@@ -131,6 +133,7 @@
         mTrustedUserPresenceRequired = trustedUserPresenceRequired;
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
         mUserConfirmationRequired = userConfirmationRequired;
+        mSecurityLevel = securityLevel;
     }
 
     /**
@@ -144,7 +147,10 @@
      * Returns {@code true} if the key resides inside secure hardware (e.g., Trusted Execution
      * Environment (TEE) or Secure Element (SE)). Key material of such keys is available in
      * plaintext only inside the secure hardware and is not exposed outside of it.
+     *
+     * @deprecated This method is superseded by @see getSecurityLevel.
      */
+    @Deprecated
     public boolean isInsideSecureHardware() {
         return mInsideSecureHardware;
     }
@@ -355,4 +361,17 @@
     public boolean isTrustedUserPresenceRequired() {
         return mTrustedUserPresenceRequired;
     }
+
+    /**
+     * Returns the security level that the key is protected by.
+     * {@code KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT} and
+     * {@code KeyProperties.SecurityLevelEnum.STRONGBOX} indicate that the key material resides
+     * in secure hardware. Key material of such keys is available in
+     * plaintext only inside the secure hardware and is not exposed outside of it.
+     *
+     * <p>See {@link KeyProperties}.{@code SecurityLevelEnum} constants.
+     */
+    public @KeyProperties.SecurityLevelEnum int getSecurityLevel() {
+        return mSecurityLevel;
+    }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 9050c69..5928540 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.security.KeyStore;
 import android.security.keymaster.KeymasterDefs;
 
 import libcore.util.EmptyArray;
@@ -857,4 +858,43 @@
         }
     }
 
+    /**
+     * This value indicates the implicit keystore namespace of the calling application.
+     * It is used by default. Only select system components can choose a different namespace
+     * which it must be configured in SEPolicy.
+     * @hide
+     */
+    public static final int NAMESPACE_APPLICATION = -1;
+
+    /**
+     * For legacy support, translate namespaces into known UIDs.
+     * @hide
+     */
+    public static int namespaceToLegacyUid(int namespace) {
+        switch (namespace) {
+            case NAMESPACE_APPLICATION:
+                return KeyStore.UID_SELF;
+            // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
+            //  b/171305388 and b/171305607
+            default:
+                throw new IllegalArgumentException("No UID corresponding to namespace "
+                        + namespace);
+        }
+    }
+
+    /**
+     * For legacy support, translate namespaces into known UIDs.
+     * @hide
+     */
+    public static int legacyUidToNamespace(int uid) {
+        switch (uid) {
+            case KeyStore.UID_SELF:
+                return NAMESPACE_APPLICATION;
+            // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
+            //  b/171305388 and b/171305607
+            default:
+                throw new IllegalArgumentException("No namespace corresponding to uid "
+                        + uid);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4390004..36da7aa 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,5 @@
 # sysui owners
 hwwang@google.com
 mrenouf@google.com
-winsonc@google.com
\ No newline at end of file
+winsonc@google.com
+madym@google.com
diff --git a/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
index 0c1d1a5..d19b653 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
@@ -16,14 +16,16 @@
   -->
 <com.android.wm.shell.bubbles.BubbleMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="#66000000"
     android:visibility="gone"
-    android:id="@+id/bubble_menu_container">
+    android:id="@+id/bubble_menu_container"
+    tools:ignore="MissingClass">
 
     <FrameLayout
-        android:layout_height="@dimen/individual_bubble_size"
+        android:layout_height="@dimen/bubble_menu_item_height"
         android:layout_width="wrap_content"
         android:background="#FFFFFF"
         android:id="@+id/bubble_menu_view">
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
index 61000fe..e392cdc2 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
@@ -17,6 +17,6 @@
 <com.android.wm.shell.bubbles.BadgedImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/bubble_overflow_button"
-    android:layout_width="@dimen/individual_bubble_size"
-    android:layout_height="@dimen/individual_bubble_size"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:src="@drawable/bubble_ic_overflow_button"/>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_view.xml b/libs/WindowManager/Shell/res/layout/bubble_view.xml
index a28bd678..2b4b9e9 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_view.xml
@@ -17,5 +17,5 @@
 <com.android.wm.shell.bubbles.BadgedImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/bubble_view"
-    android:layout_width="@dimen/individual_bubble_size"
-    android:layout_height="@dimen/individual_bubble_size"/>
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"/>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8a60aaf..25d034c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -99,12 +99,12 @@
     <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
     <!-- Padding between status bar and bubbles when displayed in expanded state -->
     <dimen name="bubble_padding_top">16dp</dimen>
+    <!-- Max amount of space between bubbles when expanded. -->
+    <dimen name="bubble_max_spacing">16dp</dimen>
     <!-- Size of individual bubbles. -->
     <dimen name="individual_bubble_size">60dp</dimen>
     <!-- Size of bubble bitmap. -->
     <dimen name="bubble_bitmap_size">52dp</dimen>
-    <!-- Size of bubble icon bitmap. -->
-    <dimen name="bubble_overflow_icon_bitmap_size">24dp</dimen>
     <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
     <dimen name="bubble_touch_padding">12dp</dimen>
     <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
@@ -136,6 +136,8 @@
     <dimen name="bubble_dismiss_slop">16dp</dimen>
     <!-- Height of button allowing users to adjust settings for bubbles. -->
     <dimen name="bubble_manage_button_height">48dp</dimen>
+    <!-- Height of an item in the bubble manage menu. -->
+    <dimen name="bubble_menu_item_height">60dp</dimen>
     <!-- Max width of the message bubble-->
     <dimen name="bubble_message_max_width">144dp</dimen>
     <!-- Min width of the message bubble -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
index 4ba84223..bb9accd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
@@ -16,8 +16,7 @@
 
 package com.android.wm.shell;
 
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -33,16 +32,19 @@
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
     private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<HideDisplayCutout> mHideDisplayCutout;
     private final ShellTaskOrganizer mShellTaskOrganizer;
 
     public ShellDump(ShellTaskOrganizer shellTaskOrganizer,
             Optional<SplitScreen> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional) {
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mSplitScreenOptional = splitScreenOptional;
         mPipOptional = pipOptional;
         mOneHandedOptional = oneHandedOptional;
+        mHideDisplayCutout = hideDisplayCutout;
     }
 
     public void dump(PrintWriter pw) {
@@ -52,5 +54,6 @@
         mPipOptional.ifPresent(pip -> pip.dump(pw));
         mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
         mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+        mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 4d06c03..32c6e36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -31,7 +31,6 @@
 import android.widget.ImageView;
 
 import com.android.launcher3.icons.DotRenderer;
-import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
 
 import java.util.EnumSet;
@@ -75,9 +74,8 @@
     private boolean mDotIsAnimating = false;
 
     private BubbleViewProvider mBubble;
+    private BubblePositioner mPositioner;
 
-    private int mBubbleBitmapSize;
-    private int mBubbleSize;
     private DotRenderer mDotRenderer;
     private DotRenderer.DrawParams mDrawParams;
     private boolean mOnLeft;
@@ -101,18 +99,21 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mBubbleBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
-        mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
         mDrawParams = new DotRenderer.DrawParams();
 
-        Path iconPath = PathParser.createPathFromPathData(
-                getResources().getString(com.android.internal.R.string.config_icon_mask));
-        mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
-
         setFocusable(true);
         setClickable(true);
     }
 
+    public void initialize(BubblePositioner positioner) {
+        mPositioner = positioner;
+
+        Path iconPath = PathParser.createPathFromPathData(
+                getResources().getString(com.android.internal.R.string.config_icon_mask));
+        mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
+                iconPath, DEFAULT_PATH_SIZE);
+    }
+
     public void showDotAndBadge(boolean onLeft) {
         removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
         animateDotBadgePositions(onLeft);
@@ -186,7 +187,8 @@
      * @param iconPath The new icon path to use when calculating dot position.
      */
     void drawDot(Path iconPath) {
-        mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
+        mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
+                iconPath, DEFAULT_PATH_SIZE);
         invalidate();
     }
 
@@ -310,13 +312,13 @@
 
         bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
         bubbleCanvas.setBitmap(bubble);
-
-        final int badgeSize = (int) (ICON_BADGE_SCALE * mBubbleSize);
+        final int bubbleSize = bubble.getWidth();
+        final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
         if (mOnLeft) {
-            badge.setBounds(0, mBubbleSize - badgeSize, badgeSize, mBubbleSize);
+            badge.setBounds(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
         } else {
-            badge.setBounds(mBubbleSize - badgeSize, mBubbleSize - badgeSize,
-                    mBubbleSize, mBubbleSize);
+            badge.setBounds(bubbleSize - badgeSize, bubbleSize - badgeSize,
+                    bubbleSize, bubbleSize);
         }
         badge.draw(bubbleCanvas);
         bubbleCanvas.setBitmap(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 93ed395..122f917 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -310,7 +310,7 @@
      *
      * @param callback the callback to notify one the bubble is ready to be displayed.
      * @param context the context for the bubble.
-     * @param controller
+     * @param controller the bubble controller.
      * @param stackView the stackView the bubble is eventually added to.
      * @param iconFactory the iconfactory use to create badged images for the bubble.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 05acb55..eb3a3a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -95,12 +95,6 @@
     private BubblePositioner mBubblePositioner;
     private SysuiProxy mSysuiProxy;
 
-    /**
-     * The relative position of the stack when we removed it and nulled it out. If the stack is
-     * re-created, it will re-appear at this position.
-     */
-    @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;
-
     // Tracks the id of the current (foreground) user.
     private int mCurrentUserId;
     // Saves notification keys of active bubbles when users are switched.
@@ -176,12 +170,12 @@
             Handler mainHandler,
             ShellTaskOrganizer organizer) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
-        return new BubbleController(context,
-                new BubbleData(context, logger), synchronizer,
-                floatingContentCoordinator, new BubbleDataRepository(context, launcherApps),
-                statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, logger, mainHandler, organizer,
-                new BubblePositioner(context, windowManager));
+        BubblePositioner positioner = new BubblePositioner(context, windowManager);
+        BubbleData data = new BubbleData(context, logger, positioner);
+        return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
+                new BubbleDataRepository(context, launcherApps),
+                statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
+                logger, mainHandler, organizer, positioner);
     }
 
     /**
@@ -207,6 +201,7 @@
         mLogger = bubbleLogger;
         mMainHandler = mainHandler;
 
+        mBubblePositioner = positioner;
         mBubbleData = data;
         mBubbleData.setListener(mBubbleDataListener);
         mBubbleData.setSuppressionChangedListener(bubble -> {
@@ -249,7 +244,6 @@
 
         mBubbleIconFactory = new BubbleIconFactory(context);
         mTaskOrganizer = organizer;
-        mBubblePositioner = positioner;
 
         launcherApps.registerCallback(new LauncherApps.Callback() {
             @Override
@@ -388,8 +382,6 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(
                     mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
-            mStackView.setStackStartPosition(mPositionFromRemovedStack);
-            mStackView.addView(mBubbleScrim);
             mStackView.onOrientationChanged();
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -429,7 +421,11 @@
 
         try {
             mAddedToWindowManager = true;
+            mBubbleData.getOverflow().initialize(this);
+            mStackView.addView(mBubbleScrim);
             mWindowManager.addView(mStackView, mWmLayoutParams);
+            // Position info is dependent on us being attached to a window
+            mBubblePositioner.update(mOrientation);
         } catch (IllegalStateException e) {
             // This means the stack has already been added. This shouldn't happen...
             e.printStackTrace();
@@ -449,10 +445,9 @@
         try {
             mAddedToWindowManager = false;
             if (mStackView != null) {
-                mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
                 mWindowManager.removeView(mStackView);
                 mStackView.removeView(mBubbleScrim);
-                mStackView = null;
+                mBubbleData.getOverflow().cleanUpExpandedState();
             } else {
                 Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
             }
@@ -544,7 +539,7 @@
             }
             if (newConfig.fontScale != mFontScale) {
                 mFontScale = newConfig.fontScale;
-                mStackView.updateFlyout(mFontScale);
+                mStackView.updateFontScale(mFontScale);
             }
             if (newConfig.getLayoutDirection() != mLayoutDirection) {
                 mLayoutDirection = newConfig.getLayoutDirection();
@@ -584,7 +579,7 @@
         if (mStackView == null) {
             return false;
         }
-        return mBubbleData.hasBubbles();
+        return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index b6a97e2..e24ff06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -69,7 +69,7 @@
         boolean selectionChanged;
         boolean orderChanged;
         boolean expanded;
-        @Nullable Bubble selectedBubble;
+        @Nullable BubbleViewProvider selectedBubble;
         @Nullable Bubble addedBubble;
         @Nullable Bubble updatedBubble;
         @Nullable Bubble addedOverflowBubble;
@@ -116,13 +116,15 @@
     }
 
     private final Context mContext;
+    private final BubblePositioner mPositioner;
     /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
     /** Bubbles that aged out to overflow. */
     private final List<Bubble> mOverflowBubbles;
     /** Bubbles that are being loaded but haven't been added to the stack just yet. */
     private final HashMap<String, Bubble> mPendingBubbles;
-    private Bubble mSelectedBubble;
+    private BubbleViewProvider mSelectedBubble;
+    private final BubbleOverflow mOverflow;
     private boolean mShowingOverflow;
     private boolean mExpanded;
     private final int mMaxBubbles;
@@ -153,9 +155,11 @@
      */
     private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
 
-    public BubbleData(Context context, BubbleLogger bubbleLogger) {
+    public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner) {
         mContext = context;
         mLogger = bubbleLogger;
+        mPositioner = positioner;
+        mOverflow = new BubbleOverflow(context, positioner);
         mBubbles = new ArrayList<>();
         mOverflowBubbles = new ArrayList<>();
         mPendingBubbles = new HashMap<>();
@@ -178,6 +182,10 @@
         return !mBubbles.isEmpty();
     }
 
+    public boolean hasOverflowBubbles() {
+        return !mOverflowBubbles.isEmpty();
+    }
+
     public boolean isExpanded() {
         return mExpanded;
     }
@@ -195,10 +203,14 @@
     }
 
     @Nullable
-    public Bubble getSelectedBubble() {
+    public BubbleViewProvider getSelectedBubble() {
         return mSelectedBubble;
     }
 
+    public BubbleOverflow getOverflow() {
+        return mOverflow;
+    }
+
     /** Return a read-only current active bubble lists. */
     public List<Bubble> getActiveBubbles() {
         return Collections.unmodifiableList(mBubbles);
@@ -212,7 +224,7 @@
         dispatchPendingChanges();
     }
 
-    public void setSelectedBubble(Bubble bubble) {
+    public void setSelectedBubble(BubbleViewProvider bubble) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubble: " + bubble);
         }
@@ -224,6 +236,10 @@
         mShowingOverflow = showingOverflow;
     }
 
+    boolean isShowingOverflow() {
+        return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
+    }
+
     /**
      * Constructs a new bubble or returns an existing one. Does not add new bubbles to
      * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
@@ -264,8 +280,8 @@
 
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
-     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
-     * BubbleStackView, BubbleIconFactory, boolean).
+     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
+     * BubbleIconFactory, boolean)
      */
     void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
         if (DEBUG_BUBBLE_DATA) {
@@ -484,10 +500,17 @@
         Bubble bubbleToRemove = mBubbles.get(indexToRemove);
         bubbleToRemove.stopInflation();
         if (mBubbles.size() == 1) {
-            // Going to become empty, handle specially.
-            setExpandedInternal(false);
-            // Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate
-            mSelectedBubble = null;
+            if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
+                // No more active bubbles but we have stuff in the overflow -- select that view
+                // if we're already expanded or always showing.
+                setShowingOverflow(true);
+                setSelectedBubbleInternal(mOverflow);
+            } else {
+                setExpandedInternal(false);
+                // Don't use setSelectedBubbleInternal because we don't want to trigger an
+                // applyUpdate
+                mSelectedBubble = null;
+            }
         }
         if (indexToRemove < mBubbles.size() - 1) {
             // Removing anything but the last bubble means positions will change.
@@ -505,7 +528,7 @@
         if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
             // Move selection to the new bubble at the same position.
             int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
-            Bubble newSelected = mBubbles.get(newIndex);
+            BubbleViewProvider newSelected = mBubbles.get(newIndex);
             setSelectedBubbleInternal(newSelected);
         }
         maybeSendDeleteIntent(reason, bubbleToRemove);
@@ -564,7 +587,7 @@
      *
      * @param bubble the new selected bubble
      */
-    private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
+    private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
         }
@@ -572,14 +595,17 @@
             return;
         }
         // Otherwise, if we are showing the overflow menu, return to the previously selected bubble.
-
-        if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
+        boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
+        if (bubble != null
+                && !mBubbles.contains(bubble)
+                && !mOverflowBubbles.contains(bubble)
+                && !isOverflow) {
             Log.e(TAG, "Cannot select bubble which doesn't exist!"
                     + " (" + bubble + ") bubbles=" + mBubbles);
             return;
         }
-        if (mExpanded && bubble != null) {
-            bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+        if (mExpanded && bubble != null && !isOverflow) {
+            ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
         }
         mSelectedBubble = bubble;
         mStateChange.selectedBubble = bubble;
@@ -629,7 +655,7 @@
             return;
         }
         if (shouldExpand) {
-            if (mBubbles.isEmpty()) {
+            if (mBubbles.isEmpty() && !mShowingOverflow) {
                 Log.e(TAG, "Attempt to expand stack when empty!");
                 return;
             }
@@ -637,7 +663,9 @@
                 Log.e(TAG, "Attempt to expand stack without selected bubble!");
                 return;
             }
-            mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+            if (mSelectedBubble instanceof Bubble) {
+                ((Bubble) mSelectedBubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
+            }
             mStateChange.orderChanged |= repackAll();
         } else if (!mBubbles.isEmpty()) {
             // Apply ordering and grouping rules from expanded -> collapsed, then save
@@ -647,14 +675,18 @@
 
             if (mShowingOverflow) {
                 // Show previously selected bubble instead of overflow menu on next expansion.
-                setSelectedBubbleInternal(mSelectedBubble);
+                if (!mSelectedBubble.getKey().equals(mOverflow.getKey())) {
+                    setSelectedBubbleInternal(mSelectedBubble);
+                } else {
+                    setSelectedBubbleInternal(mBubbles.get(0));
+                }
             }
             if (mBubbles.indexOf(mSelectedBubble) > 0) {
                 // Move the selected bubble to the top while collapsed.
                 int index = mBubbles.indexOf(mSelectedBubble);
                 if (index != 0) {
-                    mBubbles.remove(mSelectedBubble);
-                    mBubbles.add(0, mSelectedBubble);
+                    mBubbles.remove((Bubble) mSelectedBubble);
+                    mBubbles.add(0, (Bubble) mSelectedBubble);
                     mStateChange.orderChanged = true;
                 }
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 53f4e87..dc2ace9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -45,6 +45,7 @@
     static final boolean DEBUG_EXPERIMENTS = true;
     static final boolean DEBUG_OVERFLOW = false;
     static final boolean DEBUG_USER_EDUCATION = false;
+    static final boolean DEBUG_POSITIONER = false;
 
     private static final boolean FORCE_SHOW_USER_EDUCATION = false;
     private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 74521c7..646e75a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -639,7 +639,9 @@
     }
 
     /**
-     * Cleans up anything related to the task and TaskView.
+     * Cleans up anything related to the task and TaskView. If this view should be reused after this
+     * method is called, then {@link #initialize(BubbleController, BubbleStackView)} must be invoked
+     * first.
      */
     public void cleanUpExpandedState() {
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 460e0e7..19c3cf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -68,9 +68,8 @@
     private final int mFlyoutPadding;
     private final int mFlyoutSpaceFromBubble;
     private final int mPointerSize;
-    private final int mBubbleSize;
-    private final int mBubbleBitmapSize;
-    private final float mBubbleIconTopPadding;
+    private int mBubbleSize;
+    private int mBubbleBitmapSize;
 
     private final int mFlyoutElevation;
     private final int mBubbleElevation;
@@ -83,9 +82,9 @@
     private final TextView mMessageText;
 
     /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
-    private final float mNewDotRadius;
-    private final float mNewDotSize;
-    private final float mOriginalDotSize;
+    private float mNewDotRadius;
+    private float mNewDotSize;
+    private float mOriginalDotSize;
 
     /**
      * The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -169,17 +168,9 @@
         mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
         mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
 
-        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
-        mBubbleIconTopPadding  = (mBubbleSize - mBubbleBitmapSize) / 2f;
-
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
 
-        mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
-        mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
-        mNewDotSize = mNewDotRadius * 2f;
-
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {
                         android.R.attr.colorBackgroundFloating,
@@ -306,7 +297,15 @@
             @Nullable Runnable onLayoutComplete,
             @Nullable Runnable onHide,
             float[] dotCenter,
-            boolean hideDot)  {
+            boolean hideDot,
+            BubblePositioner positioner)  {
+
+        mBubbleBitmapSize = positioner.getBubbleBitmapSize();
+        mBubbleSize = positioner.getBubbleSize();
+
+        mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
+        mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
+        mNewDotSize = mNewDotRadius * 2f;
 
         updateFlyoutMessage(flyoutMessage, parentWidth);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 686d2d4..8ab2f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,14 +29,18 @@
 import android.util.PathParser
 import android.util.TypedValue
 import android.view.LayoutInflater
-import android.view.View
 import android.widget.FrameLayout
 import com.android.wm.shell.R
 
+/**
+ * The icon in the bubble overflow is scaled down, this is the percent of the normal bubble bitmap
+ * size to use.
+ */
+const val ICON_BITMAP_SIZE_PERCENT = 0.46f
+
 class BubbleOverflow(
     private val context: Context,
-    private val controller: BubbleController,
-    private val stack: BubbleStackView
+    private val positioner: BubblePositioner
 ) : BubbleViewProvider {
 
     private lateinit var bitmap: Bitmap
@@ -48,41 +52,42 @@
     private var showDot = false
 
     private val inflater: LayoutInflater = LayoutInflater.from(context)
-    private val expandedView: BubbleExpandedView = inflater
-        .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */)
-            as BubbleExpandedView
-    private val overflowBtn: BadgedImageView = inflater
-        .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */)
-            as BadgedImageView
+    private var expandedView: BubbleExpandedView?
+    private var overflowBtn: BadgedImageView?
+
     init {
         updateResources()
-        with(expandedView) {
-            initialize(controller, stack)
-            setOverflow(true)
-            applyThemeAttrs()
-        }
-        with(overflowBtn) {
-            setContentDescription(context.resources.getString(
-                R.string.bubble_overflow_button_content_description))
-            updateBtnTheme()
-        }
+        bitmapSize = positioner.bubbleBitmapSize
+        iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt()
+        expandedView = null
+        overflowBtn = null
+    }
+
+    /** Call before use and again if cleanUpExpandedState was called.  */
+    fun initialize(controller: BubbleController) {
+        getExpandedView()?.initialize(controller, controller.stackView)
+        getExpandedView()?.setOverflow(true)
+    }
+
+    fun cleanUpExpandedState() {
+        expandedView?.cleanUpExpandedState()
+        expandedView = null
     }
 
     fun update() {
         updateResources()
-        expandedView.applyThemeAttrs()
+        getExpandedView()?.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
-        overflowBtn.setImageResource(R.drawable.bubble_ic_overflow_button)
+        getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
     }
 
     fun updateResources() {
-        bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size)
-        iconBitmapSize = context.resources.getDimensionPixelSize(
-                R.dimen.bubble_overflow_icon_bitmap_size)
-        val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size)
-        overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
-        expandedView.updateDimensions()
+        bitmapSize = positioner.bubbleBitmapSize
+        iconBitmapSize = (bitmapSize * 0.46f).toInt()
+        val bubbleSize = positioner.bubbleSize
+        overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
+        expandedView?.updateDimensions()
     }
 
     private fun updateBtnTheme() {
@@ -92,7 +97,7 @@
         val typedValue = TypedValue()
         context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
         val colorAccent = res.getColor(typedValue.resourceId)
-        overflowBtn.drawable?.setTint(colorAccent)
+        overflowBtn?.drawable?.setTint(colorAccent)
         dotColor = colorAccent
 
         val iconFactory = BubbleIconFactory(context)
@@ -103,7 +108,7 @@
         val bg = ColorDrawable(res.getColor(
             if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))
 
-        val fg = InsetDrawable(overflowBtn.drawable,
+        val fg = InsetDrawable(overflowBtn?.drawable,
             bitmapSize - iconBitmapSize /* inset */)
         bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
             null /* user */, true /* shrinkNonAdaptiveIcons */).icon
@@ -111,7 +116,7 @@
         // Update dot path
         dotPath = PathParser.createPathFromPathData(
             res.getString(com.android.internal.R.string.config_icon_mask))
-        val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(),
+        val scale = iconFactory.normalizer.getScale(getIconView()!!.getDrawable(),
             null /* outBounds */, null /* path */, null /* outMaskShape */)
         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
         val matrix = Matrix()
@@ -120,20 +125,26 @@
         dotPath.transform(matrix)
 
         // Attach BubbleOverflow to BadgedImageView
-        overflowBtn.setRenderedBubble(this)
-        overflowBtn.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
+        overflowBtn?.setRenderedBubble(this)
+        overflowBtn?.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
     }
 
     fun setVisible(visible: Int) {
-        overflowBtn.visibility = visible
+        overflowBtn?.visibility = visible
     }
 
     fun setShowDot(show: Boolean) {
         showDot = show
-        overflowBtn.updateDotVisibility(true /* animate */)
+        overflowBtn?.updateDotVisibility(true /* animate */)
     }
 
     override fun getExpandedView(): BubbleExpandedView? {
+        if (expandedView == null) {
+            expandedView = inflater.inflate(R.layout.bubble_expanded_view,
+                    null /* root */, false /* attachToRoot */) as BubbleExpandedView
+            expandedView?.applyThemeAttrs()
+            updateResources()
+        }
         return expandedView
     }
 
@@ -158,10 +169,20 @@
     }
 
     override fun setContentVisibility(visible: Boolean) {
-        expandedView.setContentVisibility(visible)
+        expandedView?.setContentVisibility(visible)
     }
 
-    override fun getIconView(): View? {
+    override fun getIconView(): BadgedImageView? {
+        if (overflowBtn == null) {
+            overflowBtn = inflater.inflate(R.layout.bubble_overflow_button,
+                    null /* root */, false /* attachToRoot */) as BadgedImageView
+            overflowBtn?.initialize(positioner)
+            overflowBtn?.setContentDescription(context.resources.getString(
+                    R.string.bubble_overflow_button_content_description))
+            val bubbleSize = positioner.bubbleSize
+            overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
+            updateBtnTheme()
+        }
         return overflowBtn
     }
 
@@ -170,7 +191,7 @@
     }
 
     override fun getTaskId(): Int {
-        return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
+        return if (expandedView != null) expandedView!!.getTaskId() else INVALID_TASK_ID
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
index 2759b59..cfd0066 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
@@ -111,11 +111,11 @@
             IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
             if (binder instanceof ObjectWrapper) {
                 mController = ((ObjectWrapper<BubbleController>) binder).get();
+                updateOverflow();
             }
         } else {
             Log.w(TAG, "Bubble overflow activity created without bubble controller!");
         }
-        updateOverflow();
     }
 
     void updateOverflow() {
@@ -138,7 +138,9 @@
         final int viewHeight = recyclerViewHeight / rows;
 
         mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
-                mController::promoteBubbleFromOverflow, viewWidth, viewHeight);
+                mController::promoteBubbleFromOverflow,
+                mController.getPositioner(),
+                viewWidth, viewHeight);
         mRecyclerView.setAdapter(mAdapter);
 
         mOverflowBubbles.clear();
@@ -257,15 +259,20 @@
 
     private Context mContext;
     private Consumer<Bubble> mPromoteBubbleFromOverflow;
+    private BubblePositioner mPositioner;
     private List<Bubble> mBubbles;
     private int mWidth;
     private int mHeight;
 
-    public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
+    BubbleOverflowAdapter(Context context,
+            List<Bubble> list,
+            Consumer<Bubble> promoteBubble,
+            BubblePositioner positioner,
             int width, int height) {
         mContext = context;
         mBubbles = list;
         mPromoteBubbleFromOverflow = promoteBubble;
+        mPositioner = positioner;
         mWidth = width;
         mHeight = height;
     }
@@ -295,7 +302,7 @@
         TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
         viewName.setTextColor(textColor);
 
-        return new ViewHolder(overflowView);
+        return new ViewHolder(overflowView, mPositioner);
     }
 
     @Override
@@ -348,9 +355,10 @@
         public BadgedImageView iconView;
         public TextView textView;
 
-        public ViewHolder(LinearLayout v) {
+        ViewHolder(LinearLayout v, BubblePositioner positioner) {
             super(v);
             iconView = v.findViewById(R.id.bubble_view);
+            iconView.initialize(positioner);
             textView = v.findViewById(R.id.bubble_view_name);
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index eccd009..46e8e11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,45 +16,99 @@
 
 package com.android.wm.shell.bubbles;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.wm.shell.R;
+
+import java.lang.annotation.Retention;
+
 /**
  * Keeps track of display size, configuration, and specific bubble sizes. One place for all
  * placement and positioning calculations to refer to.
  */
 public class BubblePositioner {
+    private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
+            ? "BubblePositioner"
+            : BubbleDebugConfig.TAG_BUBBLES;
 
+    @Retention(SOURCE)
+    @IntDef({TASKBAR_POSITION_RIGHT, TASKBAR_POSITION_LEFT, TASKBAR_POSITION_BOTTOM})
+    @interface TaskbarPosition {}
+    public static final int TASKBAR_POSITION_RIGHT = 0;
+    public static final int TASKBAR_POSITION_LEFT = 1;
+    public static final int TASKBAR_POSITION_BOTTOM = 2;
+
+    /**
+     * The bitmap in the bubble is slightly smaller than the overall size of the bubble.
+     * This is the percentage to scale the image down based on the overall bubble size.
+     */
+    private static final float BUBBLE_BITMAP_SIZE_PERCENT = 0.86f;
+
+    private Context mContext;
     private WindowManager mWindowManager;
     private Rect mPositionRect;
     private int mOrientation;
     private Insets mInsets;
 
+    private int mBubbleSize;
+    private int mBubbleBitmapSize;
+
+    private PointF mPinLocation;
+    private PointF mRestingStackPosition;
+
+    private boolean mShowingInTaskbar;
+    private @TaskbarPosition int mTaskbarPosition;
+    private int mTaskbarIconSize;
+
     public BubblePositioner(Context context, WindowManager windowManager) {
+        mContext = context;
         mWindowManager = windowManager;
         update(Configuration.ORIENTATION_UNDEFINED);
     }
 
+    /**
+     * Updates orientation, available space, and inset information. Call this when config changes
+     * occur or when added to a window.
+     */
     public void update(int orientation) {
         WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
-        mPositionRect = new Rect(windowMetrics.getBounds());
+        if (windowMetrics == null) {
+            return;
+        }
         WindowInsets metricInsets = windowMetrics.getWindowInsets();
 
         Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
                 | WindowInsets.Type.statusBars()
                 | WindowInsets.Type.displayCutout());
-        update(orientation, insets, windowMetrics.getBounds());
+
+        if (BubbleDebugConfig.DEBUG_POSITIONER) {
+            Log.w(TAG, "update positioner:"
+                    + " landscape= " + (orientation == Configuration.ORIENTATION_LANDSCAPE)
+                    + " insets: " + insets
+                    + " bounds: " + windowMetrics.getBounds()
+                    + " showingInTaskbar: " + mShowingInTaskbar);
+        }
+        updateInternal(orientation, insets, windowMetrics.getBounds());
     }
 
     @VisibleForTesting
-    public void update(int orientation, Insets insets, Rect bounds) {
+    public void updateInternal(int orientation, Insets insets, Rect bounds) {
         mOrientation = orientation;
         mInsets = insets;
 
@@ -63,27 +117,156 @@
         mPositionRect.top += mInsets.top;
         mPositionRect.right -= mInsets.right;
         mPositionRect.bottom -= mInsets.bottom;
+
+        Resources res = mContext.getResources();
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+        adjustForTaskbar();
     }
 
     /**
-     * @return a rect of available screen space for displaying bubbles in the correct orientation,
-     * accounting for system bars and cutouts.
+     * Updates position information to account for taskbar state.
+     *
+     * @param taskbarPosition which position the taskbar is displayed in.
+     * @param showingInTaskbar whether the taskbar is being shown.
+     */
+    public void updateForTaskbar(int iconSize,
+            @TaskbarPosition int taskbarPosition, boolean showingInTaskbar) {
+        mShowingInTaskbar = showingInTaskbar;
+        mTaskbarIconSize =  iconSize;
+        mTaskbarPosition = taskbarPosition;
+        update(mOrientation);
+    }
+
+    /**
+     * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
+     * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
+     * and insets to account for the taskbar.
+     */
+    // TODO(b/171559950): When the insets are reported correctly we can remove this logic
+    private void adjustForTaskbar() {
+        // When bar is showing on edges... subtract that inset because we appear on top
+        if (mShowingInTaskbar && mTaskbarPosition != TASKBAR_POSITION_BOTTOM) {
+            WindowInsets metricInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            Insets navBarInsets = metricInsets.getInsetsIgnoringVisibility(
+                    WindowInsets.Type.navigationBars());
+            int newInsetLeft = mInsets.left;
+            int newInsetRight = mInsets.right;
+            if (mTaskbarPosition == TASKBAR_POSITION_LEFT) {
+                mPositionRect.left -= navBarInsets.left;
+                newInsetLeft -= navBarInsets.left;
+            } else if (mTaskbarPosition == TASKBAR_POSITION_RIGHT) {
+                mPositionRect.right += navBarInsets.right;
+                newInsetRight -= navBarInsets.right;
+            }
+            mInsets = Insets.of(newInsetLeft, mInsets.top, newInsetRight, mInsets.bottom);
+        }
+    }
+
+    /**
+     * @return a rect of available screen space accounting for orientation, system bars and cutouts.
+     * Does not account for IME.
      */
     public Rect getAvailableRect() {
         return mPositionRect;
     }
 
     /**
-     * @return the current orientation.
-     */
-    public int getOrientation() {
-        return mOrientation;
-    }
-
-    /**
-     * @return the relevant insets (status bar, nav bar, cutouts).
+     * @return the relevant insets (status bar, nav bar, cutouts). If taskbar is showing, its
+     * inset is not included here.
      */
     public Insets getInsets() {
         return mInsets;
     }
+
+    /**
+     * @return whether the device is in landscape orientation.
+     */
+    public boolean isLandscape() {
+        return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    /**
+     * Indicates how bubbles appear when expanded.
+     *
+     * When false, bubbles display at the top of the screen with the expanded view
+     * below them. When true, bubbles display at the edges of the screen with the expanded view
+     * to the left or right side.
+     */
+    public boolean showBubblesVertically() {
+        return mOrientation == Configuration.ORIENTATION_LANDSCAPE
+                || mShowingInTaskbar;
+    }
+
+    /** Size of the bubble account for badge & dot. */
+    public int getBubbleSize() {
+        int bsize = (mShowingInTaskbar && mTaskbarIconSize > 0)
+                ? mTaskbarIconSize
+                : mBubbleSize;
+        return bsize;
+    }
+
+    /** Size of the bitmap within the bubble */
+    public int getBubbleBitmapSize() {
+        float size =  (mShowingInTaskbar && mTaskbarIconSize > 0)
+                ? (mTaskbarIconSize * BUBBLE_BITMAP_SIZE_PERCENT)
+                : mBubbleBitmapSize;
+        return (int) size;
+    }
+
+    /**
+     * Sets the stack's most recent position along the edge of the screen. This is saved when the
+     * last bubble is removed, so that the stack can be restored in its previous position.
+     */
+    public void setRestingPosition(PointF position) {
+        if (mRestingStackPosition == null) {
+            mRestingStackPosition = new PointF(position);
+        } else {
+            mRestingStackPosition.set(position);
+        }
+    }
+
+    /** The position the bubble stack should rest at when collapsed. */
+    public PointF getRestingPosition() {
+        if (mPinLocation != null) {
+            return mPinLocation;
+        }
+        if (mRestingStackPosition == null) {
+            return getDefaultStartPosition();
+        }
+        return mRestingStackPosition;
+    }
+
+    /**
+     * @return the stack position to use if we don't have a saved location or if user education
+     * is being shown.
+     */
+    public PointF getDefaultStartPosition() {
+        // Start on the left if we're in LTR, right otherwise.
+        final boolean startOnLeft =
+                mContext.getResources().getConfiguration().getLayoutDirection()
+                        != View.LAYOUT_DIRECTION_RTL;
+        final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+                R.dimen.bubble_stack_starting_offset_y);
+        // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+        return new BubbleStackView.RelativeStackPosition(
+                startOnLeft,
+                startingVerticalOffset / mPositionRect.height())
+                .getAbsolutePositionInRegion(new RectF(mPositionRect));
+    }
+
+    /**
+     * @return whether the bubble stack is pinned to the taskbar.
+     */
+    public boolean showingInTaskbar() {
+        return mShowingInTaskbar;
+    }
+
+    /**
+     * In some situations bubbles will be pinned to a specific onscreen location. This sets the
+     * location to anchor the stack to.
+     */
+    public void setPinnedLocation(PointF point) {
+        mPinLocation = point;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 155f342..cbe9845 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -30,7 +30,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.ColorMatrix;
@@ -228,7 +227,7 @@
      * once it collapses.
      */
     @Nullable
-    private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+    private BubbleViewProvider mBubbleToExpandAfterFlyoutCollapse = null;
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
     private OnLayoutChangeListener mOrientationChangedListener;
@@ -572,6 +571,16 @@
                 mBubbleContainer.setActiveController(mStackAnimationController);
                 hideFlyoutImmediate();
 
+                if (!mPositioner.showingInTaskbar()) {
+                    // Also, save the magnetized stack so we can dispatch touch events to it.
+                    mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
+                            mMagneticTarget);
+                    mMagnetizedObject.setMagnetListener(mStackMagnetListener);
+                } else {
+                    // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
+                    mMagnetizedObject = null;
+                }
+
                 // Also, save the magnetized stack so we can dispatch touch events to it.
                 mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
                 mMagnetizedObject.setMagnetListener(mStackMagnetListener);
@@ -593,7 +602,9 @@
         public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
                 float viewInitialY, float dx, float dy) {
             // If we're expanding or collapsing, ignore all touch events.
-            if (mIsExpansionAnimating) {
+            if (mIsExpansionAnimating
+                    // Also ignore events if we shouldn't be draggable.
+                    || (mPositioner.showingInTaskbar() && !mIsExpanded)) {
                 return;
             }
 
@@ -603,7 +614,7 @@
             // First, see if the magnetized object consumes the event - if so, we shouldn't move the
             // bubble since it's stuck to the target.
             if (!passEventToMagnetizedObject(ev)) {
-                if (mBubbleData.isExpanded()) {
+                if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
                     mExpandedAnimationController.dragBubbleOut(
                             v, viewInitialX + dx, viewInitialY + dy);
                 } else {
@@ -701,7 +712,6 @@
         }
     };
 
-    @Nullable
     private BubbleOverflow mBubbleOverflow;
     private StackEducationView mStackEduView;
     private ManageEducationView mManageEduView;
@@ -745,7 +755,7 @@
         ta.recycle();
 
         final Runnable onBubbleAnimatedOut = () -> {
-            if (getBubbleCount() == 0) {
+            if (getBubbleCount() == 0 && !mBubbleData.isShowingOverflow()) {
                 mBubbleController.onAllBubblesAnimatedOut();
             }
         };
@@ -818,15 +828,16 @@
         setFocusable(true);
         mBubbleContainer.bringToFront();
 
-        mBubbleOverflow = new BubbleOverflow(getContext(), bubbleController, this);
+        mBubbleOverflow = mBubbleData.getOverflow();
         mBubbleContainer.addView(mBubbleOverflow.getIconView(),
                 mBubbleContainer.getChildCount() /* index */,
-                new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT));
+                new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
+                        mPositioner.getBubbleSize()));
         updateOverflow();
         mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
-            setSelectedBubble(mBubbleOverflow);
-            showManageMenu(false);
+            mBubbleData.setShowingOverflow(true);
+            mBubbleData.setSelectedBubble(mBubbleOverflow);
+            mBubbleData.setExpanded(true);
         });
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
@@ -997,11 +1008,12 @@
         mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
                 view -> {
                     showManageMenu(false /* show */);
-                    final Bubble bubble = mBubbleData.getSelectedBubble();
+                    final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
                     if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
-                        final Intent intent = bubble.getSettingsIntent(mContext);
+                        // If it's in the stack it's a proper Bubble.
+                        final Intent intent = ((Bubble) bubble).getSettingsIntent(mContext);
                         mBubbleData.setExpanded(false);
-                        mContext.startActivityAsUser(intent, bubble.getUser());
+                        mContext.startActivityAsUser(intent, ((Bubble) bubble).getUser());
                         logBubbleEvent(bubble,
                                 FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
                     }
@@ -1067,7 +1079,7 @@
             mStackEduView = new StackEducationView(mContext);
             addView(mStackEduView);
         }
-        return mStackEduView.show(mStackAnimationController.getStartPosition());
+        return mStackEduView.show(mPositioner.getDefaultStartPosition());
     }
 
     private void updateUserEdu() {
@@ -1093,7 +1105,7 @@
         addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
     }
 
-    void updateFlyout(float fontScale) {
+    void updateFontScale(float fontScale) {
         mFlyout.updateFontSize(fontScale);
     }
 
@@ -1130,7 +1142,9 @@
         Resources res = getContext().getResources();
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
 
-        mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
+        mRelativeStackPositionBeforeRotation = new RelativeStackPosition(
+                mPositioner.getRestingPosition(),
+                mStackAnimationController.getAllowableStackPositionRegion());
         mManageMenu.setVisibility(View.INVISIBLE);
         mShowingManage = false;
 
@@ -1157,7 +1171,7 @@
 
         Resources res = getContext().getResources();
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
-        mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleSize = mPositioner.getBubbleSize();
         for (Bubble b : mBubbleData.getBubbles()) {
             if (b.getIconView() == null) {
                 Log.d(TAG, "Display size changed. Icon null: " + b);
@@ -1165,6 +1179,7 @@
             }
             b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
         }
+        mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
         mExpandedAnimationController.updateResources();
         mStackAnimationController.updateResources();
         mDismissView.updateResources();
@@ -1199,8 +1214,8 @@
         super.onDetachedFromWindow();
         getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
-            mBubbleOverflow.getExpandedView().cleanUpExpandedState();
+        if (mBubbleOverflow != null) {
+            mBubbleOverflow.cleanUpExpandedState();
         }
     }
 
@@ -1390,8 +1405,7 @@
 
         if (getBubbleCount() == 0 && shouldShowStackEdu()) {
             // Override the default stack position if we're showing user education.
-            mStackAnimationController.setStackPosition(
-                    mStackAnimationController.getStartPosition());
+            mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
         }
 
         if (getBubbleCount() == 0) {
@@ -1410,7 +1424,8 @@
         bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
 
         mBubbleContainer.addView(bubble.getIconView(), 0,
-                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
+                        mPositioner.getBubbleSize()));
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -1441,10 +1456,9 @@
     }
 
     private void updateOverflowVisibility() {
-        if (mBubbleOverflow == null) {
-            return;
-        }
-        mBubbleOverflow.setVisible(mIsExpanded ? VISIBLE : GONE);
+        mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow())
+                ? VISIBLE
+                : GONE);
     }
 
     // via BubbleData.Listener
@@ -1614,6 +1628,11 @@
         mBubbleController.hideCurrentInputMethod();
     }
 
+    /** Set the stack position to whatever the positioner says. */
+    void updateStackPosition() {
+        mStackAnimationController.setStackPosition(mPositioner.getRestingPosition());
+    }
+
     private void beforeExpandedViewAnimation() {
         mIsExpansionAnimating = true;
         hideFlyoutImmediate();
@@ -1629,8 +1648,7 @@
 
     private void animateExpansion() {
         cancelDelayedExpandCollapseSwitchAnimations();
-        final boolean isLandscape =
-                mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
+        final boolean showVertically = mPositioner.showBubblesVertically();
         mIsExpanded = true;
         if (mStackEduView != null) {
             mStackEduView.hide(true /* fromExpansion */);
@@ -1650,14 +1668,19 @@
         mExpandedViewContainer.setTranslationY(getExpandedViewY());
         mExpandedViewContainer.setAlpha(1f);
 
-        // X-value of the bubble we're expanding, once it's settled in its row.
+        int index;
+        if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
+            index = mBubbleData.getBubbles().size();
+        } else {
+            index = getBubbleIndex(mExpandedBubble);
+        }
+        // Position of the bubble we're expanding, once it's settled in its row.
         final float bubbleWillBeAt =
-                mExpandedAnimationController.getBubbleXOrYForOrientation(
-                        mBubbleData.getBubbles().indexOf(mExpandedBubble));
+                mExpandedAnimationController.getBubbleXOrYForOrientation(index);
 
         // How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
         // that are animating farther, so that the expanded view doesn't move as much.
-        final float relevantStackPosition = isLandscape
+        final float relevantStackPosition = showVertically
                 ? mStackAnimationController.getStackPosition().y
                 : mStackAnimationController.getStackPosition().x;
         final float distanceAnimated = Math.abs(bubbleWillBeAt - relevantStackPosition);
@@ -1674,7 +1697,7 @@
         }
 
         // Set the pivot point for the scale, so the expanded view animates out from the bubble.
-        if (isLandscape) {
+        if (showVertically) {
             float pivotX;
             float pivotY = bubbleWillBeAt + mBubbleSize / 2f;
             if (mStackOnLeftOrWillBe) {
@@ -1709,7 +1732,7 @@
                         if (mExpandedBubble == null || mExpandedBubble.getIconView() == null) {
                             return;
                         }
-                        float translation = isLandscape
+                        float translation = showVertically
                                 ? mExpandedBubble.getIconView().getTranslationY()
                                 : mExpandedBubble.getIconView().getTranslationX();
                         mExpandedViewContainerMatrix.postTranslate(
@@ -1761,13 +1784,17 @@
         // We want to visually collapse into this bubble during the animation.
         final View expandingFromBubble = mExpandedBubble.getIconView();
 
+        int index;
+        if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
+            index = mBubbleData.getBubbles().size();
+        } else {
+            index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
+        }
         // Value the bubble is animating from (back into the stack).
         final float expandingFromBubbleAt =
-                mExpandedAnimationController.getBubbleXOrYForOrientation(
-                        mBubbleData.getBubbles().indexOf(mExpandedBubble));
-        final boolean isLandscape =
-                mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
-        if (isLandscape) {
+                mExpandedAnimationController.getBubbleXOrYForOrientation(index);
+        final boolean showVertically = mPositioner.showBubblesVertically();
+        if (mPositioner.showBubblesVertically()) {
             float pivotX;
             float pivotY = expandingFromBubbleAt + mBubbleSize / 2f;
             if (mStackOnLeftOrWillBe) {
@@ -1792,7 +1819,7 @@
                 .addUpdateListener((target, values) -> {
                     if (expandingFromBubble != null) {
                         // Follow the bubble as it translates!
-                        if (isLandscape) {
+                        if (showVertically) {
                             mExpandedViewContainerMatrix.postTranslate(
                                     0f, expandingFromBubble.getTranslationY()
                                             - expandingFromBubbleAt);
@@ -1849,7 +1876,7 @@
                 .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
                 .withEndActions(this::releaseAnimatingOutBubbleBuffer);
 
-        if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+        if (mPositioner.showBubblesVertically()) {
             float translationX = mStackAnimationController.isStackOnLeftSide()
                     ? mAnimatingOutSurfaceContainer.getTranslationX() + mBubbleSize * 2
                     : mAnimatingOutSurfaceContainer.getTranslationX();
@@ -1874,7 +1901,7 @@
         mExpandedViewContainer.setAlpha(1f);
         mExpandedViewContainer.setVisibility(View.VISIBLE);
 
-        if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+        if (mPositioner.showBubblesVertically()) {
             float pivotX;
             float pivotY = expandingFromBubbleDestination + mBubbleSize / 2f;
             if (mStackOnLeftOrWillBe) {
@@ -2113,7 +2140,7 @@
         }
     }
 
-    private void dismissBubbleIfExists(@Nullable Bubble bubble) {
+    private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
         if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
             mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE);
         }
@@ -2180,7 +2207,7 @@
      */
     float getExpandedViewY() {
         final int top = mPositioner.getAvailableRect().top;
-        if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+        if (mPositioner.showBubblesVertically()) {
             return top + mExpandedViewPadding;
         } else {
             return top + mBubbleSize + mBubblePaddingTop;
@@ -2278,7 +2305,8 @@
                         expandFlyoutAfterDelay /* onLayoutComplete */,
                         mAfterFlyoutHidden,
                         bubble.getIconView().getDotCenter(),
-                        !bubble.showDot());
+                        !bubble.showDot(),
+                        mPositioner);
             }
             mFlyout.bringToFront();
         });
@@ -2321,7 +2349,7 @@
         }
 
         if (!mIsExpanded) {
-            if (getBubbleCount() > 0) {
+            if (getBubbleCount() > 0 || mBubbleData.isShowingOverflow()) {
                 mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
                 // Increase the touch target size of the bubble
                 outRect.top -= mBubbleTouchPadding;
@@ -2550,7 +2578,7 @@
         Insets insets = mPositioner.getInsets();
         int leftPadding = insets.left + mExpandedViewPadding;
         int rightPadding = insets.right + mExpandedViewPadding;
-        if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+        if (mPositioner.showBubblesVertically()) {
             if (!mStackAnimationController.isStackOnLeftSide()) {
                 rightPadding += mPointerHeight + mBubbleSize;
             } else {
@@ -2581,7 +2609,9 @@
             bv.setZ((mMaxBubbles * mBubbleElevation) - i);
 
             if (mIsExpanded) {
-                bv.showDotAndBadge(false /* onLeft */);
+                // If we're not displaying vertically, we always show the badge on the left.
+                boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
+                bv.showDotAndBadge(onLeft);
             } else if (i == 0) {
                 bv.showDotAndBadge(!mStackOnLeftOrWillBe);
             } else {
@@ -2599,7 +2629,7 @@
             return;
         }
         float bubblePosition = mExpandedAnimationController.getBubbleXOrYForOrientation(index);
-        if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+        if (mPositioner.showBubblesVertically()) {
             float x = mStackOnLeftOrWillBe
                     ? mPositioner.getAvailableRect().left
                     : mPositioner.getAvailableRect().right
@@ -2661,21 +2691,11 @@
                 .floatValue();
     }
 
-    /** Set the start position of the bubble stack. */
-    public void setStackStartPosition(RelativeStackPosition position) {
-        mStackAnimationController.setStackStartPosition(position);
-    }
-
     /** @return the position of the bubble stack. */
     public PointF getStackPosition() {
         return mStackAnimationController.getStackPosition();
     }
 
-    /** @return the relative position of the bubble stack. */
-    public RelativeStackPosition getRelativeStackPosition() {
-        return mStackAnimationController.getRelativeStackPosition();
-    }
-
     /**
      * Logs the bubble UI event.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 0b68306..e21ba63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -134,6 +134,7 @@
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.imageView = (BadgedImageView) inflater.inflate(
                         R.layout.bubble_view, stackView, false /* attachToRoot */);
+                info.imageView.initialize(controller.getPositioner());
 
                 info.expandedView = (BubbleExpandedView) inflater.inflate(
                         R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 61fbf81..18aaa96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -84,7 +84,11 @@
     private float mBubbleSizePx;
     /** Max number of bubbles shown in row above expanded view. */
     private int mBubblesMaxRendered;
-
+    /** Max amount of space to have between bubbles when expanded. */
+    private int mBubblesMaxSpace;
+    /** Amount of space between the bubbles when expanded. */
+    private float mSpaceBetweenBubbles;
+    /** Whether the expand / collapse animation is running. */
     private boolean mAnimatingExpand = false;
 
     /**
@@ -205,8 +209,17 @@
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
-        mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleSizePx = mPositioner.getBubbleSize();
         mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
+        mBubblesMaxSpace = res.getDimensionPixelSize(R.dimen.bubble_max_spacing);
+        final float availableSpace = mPositioner.isLandscape()
+                ? mPositioner.getAvailableRect().height()
+                : mPositioner.getAvailableRect().width();
+        final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
+                + (mBubblesMaxRendered + 1) * mBubbleSizePx;
+        float spaceBetweenBubbles =
+                (availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
+        mSpaceBetweenBubbles = Math.min(spaceBetweenBubbles, mBubblesMaxSpace);
     }
 
     /**
@@ -250,13 +263,15 @@
             final Path path = new Path();
             path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
 
-            final float expandedY = getExpandedY();
+            final float expandedY = mPositioner.showBubblesVertically()
+                    ? getBubbleXOrYForOrientation(index)
+                    : getExpandedY();
             if (expanding) {
                 // If we're expanding, first draw a line from the bubble's current position to the
                 // top of the screen.
                 path.lineTo(bubble.getTranslationX(), expandedY);
                 // Then, draw a line across the screen to the bubble's resting position.
-                if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+                if (mPositioner.showBubblesVertically()) {
                     Rect availableRect = mPositioner.getAvailableRect();
                     boolean onLeft = mCollapsePoint != null
                             && mCollapsePoint.x < (availableRect.width() / 2f);
@@ -422,6 +437,9 @@
      * bubbles to accommodate it if it was previously dragged out past the threshold.
      */
     public void snapBubbleBack(View bubbleView, float velX, float velY) {
+        if (mLayout == null) {
+            return;
+        }
         final int index = mLayout.indexOfChild(bubbleView);
 
         animationForChildAtIndex(index)
@@ -580,7 +598,7 @@
                 return;
             }
 
-            if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+            if (mPositioner.showBubblesVertically()) {
                 Rect availableRect = mPositioner.getAvailableRect();
                 boolean onLeft = mCollapsePoint != null
                         && mCollapsePoint.x < (availableRect.width() / 2f);
@@ -613,23 +631,15 @@
         if (mLayout == null) {
             return 0;
         }
+        final float positionInBar = index * (mBubbleSizePx + mSpaceBetweenBubbles);
         Rect availableRect = mPositioner.getAvailableRect();
-        final boolean isLandscape =
-                mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
-        final float availableSpace = isLandscape
-                ? availableRect.height()
-                : availableRect.width();
-        final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
-                + (mBubblesMaxRendered + 1) * mBubbleSizePx;
-        final float spaceBetweenBubbles =
-                (availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
+        final boolean isLandscape = mPositioner.showBubblesVertically();
         final float expandedStackSize = (mLayout.getChildCount() * mBubbleSizePx)
-                + ((mLayout.getChildCount() - 1) * spaceBetweenBubbles);
+                + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
         final float centerPosition = isLandscape
                 ? availableRect.centerY()
                 : availableRect.centerX();
         final float rowStart = centerPosition - (expandedStackSize / 2f);
-        final float positionInBar = index * (mBubbleSizePx + spaceBetweenBubbles);
         return rowStart + positionInBar;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index d7f2e4b..24a8809d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -133,12 +133,6 @@
     /** Whether or not the stack's start position has been set. */
     private boolean mStackMovedToStartPosition = false;
 
-    /**
-     * The stack's most recent position along the edge of the screen. This is saved when the last
-     * bubble is removed, so that the stack can be restored in its previous position.
-     */
-    private PointF mRestingStackPosition;
-
     /** The height of the most recently visible IME. */
     private float mImeHeight = 0f;
 
@@ -434,6 +428,9 @@
      * Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
      */
     public PointF getStackPositionAlongNearestHorizontalEdge() {
+        if (mPositioner.showingInTaskbar()) {
+            return mPositioner.getRestingPosition();
+        }
         final PointF stackPos = getStackPosition();
         final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
         final RectF bounds = getAllowableStackPositionRegion();
@@ -447,7 +444,7 @@
         pw.println("StackAnimationController state:");
         pw.print("  isActive:             "); pw.println(isActiveController());
         pw.print("  restingStackPos:      ");
-        pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+        pw.println(mPositioner.getRestingPosition().toString());
         pw.print("  currentStackPos:      "); pw.println(mStackPosition.toString());
         pw.print("  isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
         pw.print("  withinDismiss:        "); pw.println(isStackStuckToTarget());
@@ -501,7 +498,7 @@
 
                 .addEndListener((animation, canceled, endValue, endVelocity) -> {
                     if (!canceled) {
-                        mRestingStackPosition.set(mStackPosition);
+                        mPositioner.setRestingPosition(mStackPosition);
 
                         springFirstBubbleWithStackFollowing(property, spring, endVelocity,
                                 finalPosition != null
@@ -679,7 +676,7 @@
                                 // resting position - the touch location is not a valid resting
                                 // position. We'll set this when the stack springs to the left or
                                 // right side of the screen after the touch gesture ends.
-                                mRestingStackPosition.set(mStackPosition);
+                                mPositioner.setRestingPosition(mStackPosition);
                             }
 
                             if (after != null) {
@@ -772,10 +769,9 @@
         if (getBubbleCount() > 0) {
             animationForChildAtIndex(0).translationX(mStackPosition.x).start();
         } else {
+            // TODO: still needed with positioner?
             // When all children are removed ensure stack position is sane
-            setStackPosition(mRestingStackPosition == null
-                    ? getStartPosition()
-                    : mRestingStackPosition);
+            mPositioner.setRestingPosition(mPositioner.getRestingPosition());
 
             // Remove the stack from the coordinator since we don't have any bubbles and aren't
             // visible.
@@ -848,8 +844,8 @@
         mSwapAnimationOffset = res.getDimensionPixelSize(R.dimen.bubble_swap_animation_offset);
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
-        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+        mBubbleSize = mPositioner.getBubbleSize();
+        mBubbleBitmapSize = mPositioner.getBubbleBitmapSize();
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
     }
@@ -873,9 +869,8 @@
         // Post to ensure that the layout's width and height have been calculated.
         mLayout.setVisibility(View.INVISIBLE);
         mLayout.post(() -> {
-            setStackPosition(mRestingStackPosition == null
-                    ? getStartPosition()
-                    : mRestingStackPosition);
+            setStackPosition(mPositioner.getRestingPosition());
+
             mStackMovedToStartPosition = true;
             mLayout.setVisibility(View.VISIBLE);
 
@@ -919,11 +914,7 @@
         Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
         mStackPosition.set(pos.x, pos.y);
 
-        if (mRestingStackPosition == null) {
-            mRestingStackPosition = new PointF();
-        }
-
-        mRestingStackPosition.set(mStackPosition);
+        mPositioner.setRestingPosition(mStackPosition);
 
         // If we're not the active controller, we don't want to physically move the bubble views.
         if (isActiveController()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
new file mode 100644
index 0000000..38e0519
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.hidedisplaycutout;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to engage hide display cutout feature.
+ */
+public interface HideDisplayCutout {
+    /**
+     * Notifies {@link Configuration} changed.
+     * @param newConfig
+     */
+    void onConfigurationChanged(Configuration newConfig);
+
+    /**
+     * Dumps hide display cutout status.
+     */
+    void dump(@NonNull PrintWriter pw);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
new file mode 100644
index 0000000..e4e2546
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.hidedisplaycutout;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemProperties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the hide display cutout status.
+ */
+public class HideDisplayCutoutController implements HideDisplayCutout {
+    private static final String TAG = "HideDisplayCutoutController";
+
+    private final Context mContext;
+    private final HideDisplayCutoutOrganizer mOrganizer;
+    @VisibleForTesting
+    boolean mEnabled;
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer) {
+        mContext = context;
+        mOrganizer = organizer;
+        updateStatus();
+    }
+
+    /**
+     * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
+     * supported.
+     */
+    @Nullable
+    public static HideDisplayCutoutController create(
+            Context context, DisplayController displayController) {
+        // The SystemProperty is set for devices that support this feature and is used to control
+        // whether to create the HideDisplayCutout instance.
+        // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk).
+        if (!SystemProperties.getBoolean("ro.support_hide_display_cutout", false)) {
+            return null;
+        }
+
+        HideDisplayCutoutOrganizer organizer =
+                new HideDisplayCutoutOrganizer(context, displayController);
+        return new HideDisplayCutoutController(context, organizer);
+    }
+
+    @VisibleForTesting
+    void updateStatus() {
+        // The config value is used for controlling enabling/disabling status of the feature and is
+        // defined in the config.xml in a "Hide Display Cutout" overlay package (e.g. device/google/
+        // crosshatch/crosshatch/overlay/packages/apps/overlays/NoCutoutOverlay).
+        final boolean enabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea);
+        if (enabled == mEnabled) {
+            return;
+        }
+
+        mEnabled = enabled;
+        if (enabled) {
+            mOrganizer.enableHideDisplayCutout();
+        } else {
+            mOrganizer.disableHideDisplayCutout();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        updateStatus();
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw) {
+        final String prefix = "  ";
+        pw.print(TAG);
+        pw.println(" states: ");
+        pw.print(prefix);
+        pw.print("mEnabled=");
+        pw.println(mEnabled);
+        mOrganizer.dump(pw);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
new file mode 100644
index 0000000..090d227
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.RotationUtils;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayChangeController;
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Manages the display areas of hide display cutout feature.
+ */
+class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
+    private static final String TAG = "HideDisplayCutoutOrganizer";
+
+    private final Context mContext;
+    private final DisplayController mDisplayController;
+
+    @VisibleForTesting
+    @GuardedBy("this")
+    ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaMap = new ArrayMap();
+    // The default display bound in natural orientation.
+    private final Rect mDefaultDisplayBounds = new Rect();
+    @VisibleForTesting
+    final Rect mCurrentDisplayBounds = new Rect();
+    // The default display cutout in natural orientation.
+    private Insets mDefaultCutoutInsets;
+    private Insets mCurrentCutoutInsets;
+    private boolean mIsDefaultPortrait;
+    private int mStatusBarHeight;
+    @VisibleForTesting
+    int mOffsetX;
+    @VisibleForTesting
+    int mOffsetY;
+    @VisibleForTesting
+    int mRotation;
+
+    /**
+     * Handles rotation based on OnDisplayChangingListener callback.
+     */
+    private final DisplayChangeController.OnDisplayChangingListener mRotationController =
+            (display, fromRotation, toRotation, wct) -> {
+                mRotation = toRotation;
+                updateBoundsAndOffsets(true /* enable */);
+                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                applyAllBoundsAndOffsets(wct, t);
+                // Only apply t here since the server will do the wct.apply when the method
+                // finishes.
+                t.apply();
+            };
+
+    HideDisplayCutoutOrganizer(Context context, DisplayController displayController) {
+        mContext = context;
+        mDisplayController = displayController;
+    }
+
+    @Override
+    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+            @NonNull SurfaceControl leash) {
+        if (!addDisplayAreaInfoAndLeashToMap(displayAreaInfo, leash)) {
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        applyBoundsAndOffsets(displayAreaInfo.token, leash, wct, tx);
+        applyTransaction(wct, tx);
+    }
+
+    @Override
+    public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+        synchronized (this) {
+            if (!mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+                Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
+                return;
+            }
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            applyBoundsAndOffsets(
+                    displayAreaInfo.token, mDisplayAreaMap.get(displayAreaInfo.token), wct, t);
+            applyTransaction(wct, t);
+            mDisplayAreaMap.remove(displayAreaInfo.token);
+        }
+    }
+
+    private void updateDisplayAreaMap(List<DisplayAreaAppearedInfo> displayAreaInfos) {
+        for (int i = 0; i < displayAreaInfos.size(); i++) {
+            final DisplayAreaInfo info = displayAreaInfos.get(i).getDisplayAreaInfo();
+            final SurfaceControl leash = displayAreaInfos.get(i).getLeash();
+            addDisplayAreaInfoAndLeashToMap(info, leash);
+        }
+    }
+
+    @VisibleForTesting
+    boolean addDisplayAreaInfoAndLeashToMap(@NonNull DisplayAreaInfo displayAreaInfo,
+            @NonNull SurfaceControl leash) {
+        synchronized (this) {
+            if (displayAreaInfo.displayId != DEFAULT_DISPLAY) {
+                return false;
+            }
+            if (mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+                Log.w(TAG, "Already appeared token: " + displayAreaInfo.token);
+                return false;
+            }
+            mDisplayAreaMap.put(displayAreaInfo.token, leash);
+            return true;
+        }
+    }
+
+    /**
+     * Enables hide display cutout.
+     */
+    void enableHideDisplayCutout() {
+        mDisplayController.addDisplayChangingController(mRotationController);
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display != null) {
+            mRotation = display.getRotation();
+        }
+        final List<DisplayAreaAppearedInfo> displayAreaInfos =
+                registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        updateDisplayAreaMap(displayAreaInfos);
+        updateBoundsAndOffsets(true /* enabled */);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        applyAllBoundsAndOffsets(wct, t);
+        applyTransaction(wct, t);
+    }
+
+    /**
+     * Disables hide display cutout.
+     */
+    void disableHideDisplayCutout() {
+        updateBoundsAndOffsets(false /* enabled */);
+        mDisplayController.removeDisplayChangingController(mRotationController);
+        unregisterOrganizer();
+    }
+
+    @VisibleForTesting
+    Insets getDisplayCutoutInsetsOfNaturalOrientation() {
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display == null) {
+            return Insets.NONE;
+        }
+        DisplayCutout cutout = display.getCutout();
+        Insets insets = cutout != null ? Insets.of(cutout.getSafeInsets()) : Insets.NONE;
+        return mRotation != Surface.ROTATION_0
+                ? RotationUtils.rotateInsets(insets, 4 /* total number of rotation */ - mRotation)
+                : insets;
+    }
+
+    @VisibleForTesting
+    Rect getDisplayBoundsOfNaturalOrientation() {
+        Point realSize = new Point(0, 0);
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display != null) {
+            display.getRealSize(realSize);
+        }
+        final boolean isDisplaySizeFlipped = isDisplaySizeFlipped();
+        return new Rect(
+                0,
+                0,
+                isDisplaySizeFlipped ? realSize.y : realSize.x,
+                isDisplaySizeFlipped ? realSize.x : realSize.y);
+    }
+
+    private boolean isDisplaySizeFlipped() {
+        return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+    }
+
+    /**
+     * Updates bounds and offsets according to current state.
+     *
+     * @param enabled whether the hide display cutout feature is enabled.
+     */
+    @VisibleForTesting
+    void updateBoundsAndOffsets(boolean enabled) {
+        if (!enabled) {
+            resetBoundsAndOffsets();
+        } else {
+            initDefaultValuesIfNeeded();
+
+            // Reset to default values.
+            mCurrentDisplayBounds.set(mDefaultDisplayBounds);
+            mOffsetX = 0;
+            mOffsetY = 0;
+
+            // Update bounds and insets according to the rotation.
+            mCurrentCutoutInsets = RotationUtils.rotateInsets(mDefaultCutoutInsets, mRotation);
+            if (isDisplaySizeFlipped()) {
+                mCurrentDisplayBounds.set(
+                        mCurrentDisplayBounds.top,
+                        mCurrentDisplayBounds.left,
+                        mCurrentDisplayBounds.bottom,
+                        mCurrentDisplayBounds.right);
+            }
+            mCurrentDisplayBounds.inset(mCurrentCutoutInsets);
+
+            // Replace the top bound with the max(status bar height, cutout height) if there is
+            // cutout on the top side.
+            mStatusBarHeight = getStatusBarHeight();
+            if (mCurrentCutoutInsets.top != 0) {
+                mCurrentDisplayBounds.top = Math.max(mStatusBarHeight, mCurrentCutoutInsets.top);
+            }
+            mOffsetX = mCurrentDisplayBounds.left;
+            mOffsetY = mCurrentDisplayBounds.top;
+        }
+    }
+
+    private void resetBoundsAndOffsets() {
+        mCurrentDisplayBounds.setEmpty();
+        mOffsetX = 0;
+        mOffsetY = 0;
+    }
+
+    private void initDefaultValuesIfNeeded() {
+        if (!mDefaultDisplayBounds.isEmpty()) {
+            return;
+        }
+        mDefaultDisplayBounds.set(getDisplayBoundsOfNaturalOrientation());
+        mDefaultCutoutInsets = getDisplayCutoutInsetsOfNaturalOrientation();
+        mIsDefaultPortrait = mDefaultDisplayBounds.width() < mDefaultDisplayBounds.height();
+    }
+
+    private void applyAllBoundsAndOffsets(
+            WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        synchronized (this) {
+            mDisplayAreaMap.forEach((token, leash) -> {
+                applyBoundsAndOffsets(token, leash, wct, t);
+            });
+        }
+    }
+
+    @VisibleForTesting
+    void applyBoundsAndOffsets(WindowContainerToken token, SurfaceControl leash,
+            WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        wct.setBounds(token, mCurrentDisplayBounds.isEmpty() ? null : mCurrentDisplayBounds);
+        t.setPosition(leash, mOffsetX,  mOffsetY);
+    }
+
+    @VisibleForTesting
+    void applyTransaction(WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        applyTransaction(wct);
+        t.apply();
+    }
+
+    private int getStatusBarHeight() {
+        final boolean isLandscape =
+                mIsDefaultPortrait ? isDisplaySizeFlipped() : !isDisplaySizeFlipped();
+        return mContext.getResources().getDimensionPixelSize(
+                isLandscape ? R.dimen.status_bar_height_landscape
+                        : R.dimen.status_bar_height_portrait);
+    }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String prefix = "  ";
+        pw.print(TAG);
+        pw.println(" states: ");
+        synchronized (this) {
+            pw.print(prefix);
+            pw.print("mDisplayAreaMap=");
+            pw.println(mDisplayAreaMap);
+        }
+        pw.print(prefix);
+        pw.print("getDisplayBoundsOfNaturalOrientation()=");
+        pw.println(getDisplayBoundsOfNaturalOrientation());
+        pw.print(prefix);
+        pw.print("mDefaultDisplayBounds=");
+        pw.println(mDefaultDisplayBounds);
+        pw.print(prefix);
+        pw.print("mCurrentDisplayBounds=");
+        pw.println(mCurrentDisplayBounds);
+        pw.print(prefix);
+        pw.print("mDefaultCutoutInsets=");
+        pw.println(mDefaultCutoutInsets);
+        pw.print(prefix);
+        pw.print("mCurrentCutoutInsets=");
+        pw.println(mCurrentCutoutInsets);
+        pw.print(prefix);
+        pw.print("mRotation=");
+        pw.println(mRotation);
+        pw.print(prefix);
+        pw.print("mStatusBarHeight=");
+        pw.println(mStatusBarHeight);
+        pw.print(prefix);
+        pw.print("mOffsetX=");
+        pw.println(mOffsetX);
+        pw.print(prefix);
+        pw.print("mOffsetY=");
+        pw.println(mOffsetY);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 33b6902..728cc51 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -16,9 +16,10 @@
 
 package com.android.wm.shell.flicker.pip.tv
 
-import android.os.SystemClock
 import androidx.test.filters.RequiresDevice
-import org.junit.Assert.assertNotNull
+import androidx.test.uiautomator.UiObject2
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.wait
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -34,21 +35,27 @@
 class TvPipMenuTests(rotationName: String, rotation: Int)
     : TvPipTestBase(rotationName, rotation) {
 
+    private val systemUiResources =
+            packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+    private val playButtonDescription = systemUiResources.run {
+        getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME))
+    }
+    private val pauseButtonDescription = systemUiResources.run {
+        getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME))
+    }
+
     @Before
     override fun setUp() {
         super.setUp()
         // Launch the app and go to PiP
         testApp.launchViaIntent()
-        testApp.clickEnterPipButton()
     }
 
     @Test
     fun pipMenu_open() {
-        // Pressing the Window key should bring up Pip menu
-        uiDevice.pressWindowKey()
-        val pipMenu = uiDevice.waitForTvPipMenu()
-                ?: fail("Pip notification should have been dismissed")
+        val pipMenu = enterPip_openMenu_assertShown()
 
+        // Make sure it's fullscreen
         assertTrue("Pip menu should be shown fullscreen", pipMenu.isFullscreen(uiDevice))
 
         testApp.closePipWindow()
@@ -56,22 +63,29 @@
 
     @Test
     fun pipMenu_backButton() {
-        // Pressing the Window key should bring up Pip menu
-        uiDevice.pressWindowKey()
-        assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+        enterPip_openMenu_assertShown()
 
         // Pressing the Back key should close the Pip menu
         uiDevice.pressBack()
-        assertTrue("Pip notification should have closed", uiDevice.waitForTvPipMenuToClose())
+        assertTrue("Pip menu should have closed", uiDevice.waitForTvPipMenuToClose())
+
+        testApp.closePipWindow()
+    }
+
+    @Test
+    fun pipMenu_homeButton() {
+        enterPip_openMenu_assertShown()
+
+        // Pressing the Home key should close the Pip menu
+        uiDevice.pressHome()
+        assertTrue("Pip menu should have closed", uiDevice.waitForTvPipMenuToClose())
 
         testApp.closePipWindow()
     }
 
     @Test
     fun pipMenu_closeButton() {
-        // Pressing the Window key should bring up Pip menu
-        uiDevice.pressWindowKey()
-        assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+        enterPip_openMenu_assertShown()
 
         // PiP menu should contain the Close button
         val closeButton = uiDevice.findTvPipMenuCloseButton()
@@ -84,26 +98,53 @@
 
     @Test
     fun pipMenu_fullscreenButton() {
-        // Pressing the Window key should bring up Pip menu
-        uiDevice.pressWindowKey()
-        assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+        enterPip_openMenu_assertShown()
 
         // PiP menu should contain the Fullscreen button
         val fullscreenButton = uiDevice.findTvPipMenuFullscreenButton()
                 ?: fail("\"Full screen\" button should be shown in Pip menu")
 
         // Clicking on the fullscreen button should return app to the fullscreen mode.
-        // Click, wait for 3 seconds, check the app is fullscreen
+        // Click, wait for the app to go fullscreen
         fullscreenButton.click()
-        SystemClock.sleep(3_000L)
         assertTrue("\"Full screen\" button should open the app fullscreen",
-                testApp.ui?.isFullscreen(uiDevice) ?: false)
+                wait { testApp.ui?.isFullscreen(uiDevice) ?: false })
 
         // Close the app
         uiDevice.pressBack()
         testApp.waitUntilClosed()
     }
 
+    @Test
+    fun pipMenu_mediaPlayPauseButtons() {
+        // Start media session before entering PiP
+        testApp.clickStartMediaSessionButton()
+
+        enterPip_openMenu_assertShown()
+
+        // PiP menu should contain the Pause button
+        val pauseButton = uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
+                ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
+                        "playing media session.")
+
+        // When we pause media, the button should change from Pause to Play
+        pauseButton.click()
+
+        // PiP menu should contain the Play button now
+        uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription)
+                ?: fail("\"Play\" button should be shown in Pip menu if there is an active " +
+                        "paused media session.")
+
+        testApp.closePipWindow()
+    }
+
+    private fun enterPip_openMenu_assertShown(): UiObject2 {
+        testApp.clickEnterPipButton()
+        // Pressing the Window key should bring up Pip menu
+        uiDevice.pressWindowKey()
+        return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown")
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index d9e6ff3..8db8bc67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -45,6 +45,19 @@
 fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? = findObject(
         By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID))
 
+fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? {
+    val buttonSelector = By.desc(desc)
+    val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector)
+    return findObject(menuWithButtonSelector)?.findObject(buttonSelector)
+}
+
+fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
+    val buttonSelector = By.desc(desc)
+    val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector)
+    return wait(Until.findObject(menuWithButtonSelector), WAIT_TIME_MS)
+            ?.findObject(buttonSelector)
+}
+
 fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
     height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
index f70603e..b60068a 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -17,7 +17,8 @@
 package com.android.wm.shell.flicker.testapp;
 
 import static android.media.MediaMetadata.METADATA_KEY_TITLE;
-import static android.media.session.PlaybackState.ACTION_PLAY_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PLAY;
 import static android.media.session.PlaybackState.ACTION_STOP;
 import static android.media.session.PlaybackState.STATE_PAUSED;
 import static android.media.session.PlaybackState.STATE_PLAYING;
@@ -48,7 +49,7 @@
 
     private MediaSession mMediaSession;
     private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
-            .setActions(ACTION_PLAY_PAUSE | ACTION_STOP)
+            .setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
             .setState(STATE_STOPPED, 0, 1f);
     private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
     private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 9940ea5..dca2732 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -30,6 +30,7 @@
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
         "testables",
+        "platform-test-annotations",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 7adc411..7c9b9c3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -35,6 +35,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -135,8 +136,9 @@
         mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
         mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
         mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
-
-        mBubbleData = new BubbleData(getContext(), mBubbleLogger);
+        TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
+                mock(WindowManager.class));
+        mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner);
 
         // Used by BubbleData to set lastAccessedTime
         when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
index 5b77e4a..69d5244 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
@@ -19,7 +19,9 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.graphics.Color;
 import android.graphics.PointF;
@@ -36,7 +38,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -48,11 +50,16 @@
     private TextView mSenderName;
     private float[] mDotCenter = new float[2];
     private Bubble.FlyoutMessage mFlyoutMessage;
+    @Mock
+    private BubblePositioner mPositioner;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        when(mPositioner.getBubbleBitmapSize()).thenReturn(40);
+        when(mPositioner.getBubbleSize()).thenReturn(60);
+
         mFlyoutMessage = new Bubble.FlyoutMessage();
         mFlyoutMessage.senderName = "Josh";
         mFlyoutMessage.message = "Hello";
@@ -70,7 +77,8 @@
         mFlyout.setupFlyoutStartingAsDot(
                 mFlyoutMessage,
                 new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
-                false);
+                false,
+                mPositioner);
         mFlyout.setVisibility(View.VISIBLE);
 
         assertEquals("Hello", mFlyoutText.getText());
@@ -80,10 +88,11 @@
 
     @Test
     public void testFlyoutHide_runsCallback() {
-        Runnable after = Mockito.mock(Runnable.class);
+        Runnable after = mock(Runnable.class);
         mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
                 new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter,
-                false);
+                false,
+                mPositioner);
         mFlyout.hideFlyout();
 
         verify(after).run();
@@ -93,7 +102,8 @@
     public void testSetCollapsePercent() {
         mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
                 new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
-                false);
+                false,
+                mPositioner);
         mFlyout.setVisibility(View.VISIBLE);
 
         mFlyout.setCollapsePercent(1f);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
new file mode 100644
index 0000000..96bc533
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+public class TestableBubblePositioner extends BubblePositioner {
+
+    public TestableBubblePositioner(Context context,
+            WindowManager windowManager) {
+        super(context, windowManager);
+
+        updateInternal(Configuration.ORIENTATION_PORTRAIT,
+                Insets.of(0, 0, 0, 0),
+                new Rect(0, 0, 500, 1000));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 9c4f341..1eba3c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -64,7 +64,7 @@
         super.setUp();
 
         BubblePositioner positioner = new BubblePositioner(getContext(), mock(WindowManager.class));
-        positioner.update(Configuration.ORIENTATION_PORTRAIT,
+        positioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
                 Insets.of(0, 0, 0, 0),
                 new Rect(0, 0, mDisplayWidth, mDisplayHeight));
         mExpandedController = new ExpandedAnimationController(positioner, mExpandedViewPadding,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
index 6b01462..f36dcbe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
@@ -27,6 +27,7 @@
 import android.graphics.PointF;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -34,7 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.TestableBubblePositioner;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
 import org.junit.Before;
@@ -310,7 +311,7 @@
             super(floatingContentCoordinator,
                     bubbleCountSupplier,
                     onBubbleAnimatedOutAction,
-                    mock(BubblePositioner.class));
+                    new TestableBubblePositioner(mContext, mock(WindowManager.class)));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
new file mode 100644
index 0000000..595440f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.hidedisplaycutout;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutControllerTest {
+    private TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    private HideDisplayCutoutController mHideDisplayCutoutController;
+    @Mock
+    private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mHideDisplayCutoutController = new HideDisplayCutoutController(
+                mContext, mMockDisplayAreaOrganizer);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_On() {
+        mHideDisplayCutoutController.mEnabled = false;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, true);
+        reset(mMockDisplayAreaOrganizer);
+        mHideDisplayCutoutController.updateStatus();
+        verify(mMockDisplayAreaOrganizer).enableHideDisplayCutout();
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_Off() {
+        mHideDisplayCutoutController.mEnabled = true;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, false);
+        mHideDisplayCutoutController.updateStatus();
+        verify(mMockDisplayAreaOrganizer).disableHideDisplayCutout();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
new file mode 100644
index 0000000..2e4fd6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutOrganizerTest {
+    private TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private DisplayController mMockDisplayController;
+    private HideDisplayCutoutOrganizer mOrganizer;
+
+    private DisplayAreaInfo mDisplayAreaInfo;
+    private SurfaceControl mLeash;
+
+    @Mock
+    private Display mDisplay;
+    @Mock
+    private IWindowContainerToken mMockRealToken;
+    private WindowContainerToken mToken;
+
+    private final Rect mFakeDefaultBounds = new Rect(0, 0, 100, 200);
+    private final Insets mFakeDefaultCutoutInsets = Insets.of(0, 10, 0, 0);
+    private final int mFakeStatusBarHeightPortrait = 15;
+    private final int mFakeStatusBarHeightLandscape = 10;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
+
+        HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(
+                mContext, mMockDisplayController);
+        mOrganizer = Mockito.spy(organizer);
+        doNothing().when(mOrganizer).unregisterOrganizer();
+        doNothing().when(mOrganizer).applyBoundsAndOffsets(any(), any(), any(), any());
+        doNothing().when(mOrganizer).applyTransaction(any(), any());
+
+        // It will be called when mDisplayAreaMap.containKey(token) is called.
+        Binder binder = new Binder();
+        doReturn(binder).when(mMockRealToken).asBinder();
+        mToken = new WindowContainerToken(mMockRealToken);
+        mLeash = new SurfaceControl();
+        mDisplayAreaInfo = new DisplayAreaInfo(
+                mToken, DEFAULT_DISPLAY, FEATURE_HIDE_DISPLAY_CUTOUT);
+        mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
+        DisplayAreaAppearedInfo info = new DisplayAreaAppearedInfo(mDisplayAreaInfo, mLeash);
+        ArrayList<DisplayAreaAppearedInfo> infoList = new ArrayList<>();
+        infoList.add(info);
+        doReturn(infoList).when(mOrganizer).registerOrganizer(
+                DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+    }
+
+    @Test
+    public void testEnableHideDisplayCutout() {
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isTrue();
+        assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+    }
+
+    @Test
+    public void testOnDisplayAreaAppeared() {
+        mOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mToken)).isTrue();
+        assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+    }
+
+    @Test
+    public void testOnDisplayAreaVanished() {
+        mOrganizer.mDisplayAreaMap.put(mDisplayAreaInfo.token, mLeash);
+        mOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
+
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isFalse();
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot0() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+        doReturn(Surface.ROTATION_0).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 15, 100, 200));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(15);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_0);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot90() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+        doReturn(Surface.ROTATION_90).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(10, 0, 200, 100));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(10);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_90);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot270() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+        doReturn(Surface.ROTATION_270).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 190, 100));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_270);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_disable() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+        mOrganizer.enableHideDisplayCutout();
+
+        // disable hide display cutout
+        mOrganizer.disableHideDisplayCutout();
+        verify(mOrganizer).updateBoundsAndOffsets(false);
+        verify(mOrganizer).unregisterOrganizer();
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 0, 0));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+    }
+}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 4fbcfe1..047b809 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -202,6 +202,7 @@
      *
      * @hide
      */
+    @SystemApi
     @TestApi
     public static final String FUSED_PROVIDER = "fused";
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 44890be..f216c8c 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -79,7 +79,8 @@
 /**
  * This is a class for reading and writing Exif tags in various image file formats.
  * <p>
- * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF.
+ * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF,
+ * AVIF.
  * <p>
  * Supported for writing: JPEG, PNG, WebP.
  * <p>
@@ -543,6 +544,8 @@
     private static final byte[] HEIF_TYPE_FTYP = new byte[] {'f', 't', 'y', 'p'};
     private static final byte[] HEIF_BRAND_MIF1 = new byte[] {'m', 'i', 'f', '1'};
     private static final byte[] HEIF_BRAND_HEIC = new byte[] {'h', 'e', 'i', 'c'};
+    private static final byte[] HEIF_BRAND_AVIF = new byte[] {'a', 'v', 'i', 'f'};
+    private static final byte[] HEIF_BRAND_AVIS = new byte[] {'a', 'v', 'i', 's'};
 
     // See http://fileformats.archiveteam.org/wiki/Olympus_ORF
     private static final short ORF_SIGNATURE_1 = 0x4f52;
@@ -2662,6 +2665,7 @@
             byte[] brand = new byte[4];
             boolean isMif1 = false;
             boolean isHeic = false;
+            boolean isAvif = false;
             for (long i = 0; i < chunkDataSize / 4;  ++i) {
                 if (signatureInputStream.read(brand) != brand.length) {
                     return false;
@@ -2674,8 +2678,11 @@
                     isMif1 = true;
                 } else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) {
                     isHeic = true;
+                } else if (Arrays.equals(brand, HEIF_BRAND_AVIF)
+                        || Arrays.equals(brand, HEIF_BRAND_AVIS)) {
+                    isAvif = true;
                 }
-                if (isMif1 && isHeic) {
+                if (isMif1 && (isHeic || isAvif)) {
                     return true;
                 }
             }
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fbf38dc..e6d95eb6 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -272,7 +272,8 @@
         if (mimeType.equals("image/heif")
                 || mimeType.equals("image/heif-sequence")
                 || mimeType.equals("image/heic")
-                || mimeType.equals("image/heic-sequence")) {
+                || mimeType.equals("image/heic-sequence")
+                || mimeType.equals("image/avif")) {
             try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
                 retriever.setDataSource(file.getAbsolutePath());
                 bitmap = retriever.getThumbnailImageAtIndex(-1,
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 00e3bce..f60c708 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -41,6 +41,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -69,6 +70,7 @@
 
     private final Context mContext;
     private final ISoundTriggerSession mSoundTriggerSession;
+    private final IBinder mBinderToken = new Binder();
 
     // Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
     // the createSoundTriggerDetector() call.
@@ -90,7 +92,8 @@
             originatorIdentity.packageName = ActivityThread.currentOpPackageName();
 
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-                mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity);
+                mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity,
+                        mBinderToken);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index aa3143f..97161e5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -61,6 +61,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -369,6 +370,28 @@
         mTunerResourceManager.setFrontendInfoList(infos);
     }
 
+    /**
+     * Get frontend info list from native and build them into a {@link FrontendInfo} list. Any
+     * {@code null} FrontendInfo element would be removed.
+     */
+    private FrontendInfo[] getFrontendInfoListInternal() {
+        List<Integer> ids = getFrontendIds();
+        if (ids == null) {
+            return null;
+        }
+        FrontendInfo[] infos = new FrontendInfo[ids.size()];
+        for (int i = 0; i < ids.size(); i++) {
+            int id = ids.get(i);
+            FrontendInfo frontendInfo = getFrontendInfoById(id);
+            if (frontendInfo == null) {
+                Log.e(TAG, "Failed to get a FrontendInfo on frontend id:" + id + "!");
+                continue;
+            }
+            infos[i] = frontendInfo;
+        }
+        return Arrays.stream(infos).filter(Objects::nonNull).toArray(FrontendInfo[]::new);
+    }
+
     /** @hide */
     public static int getTunerVersion() {
         return sTunerVersion;
@@ -932,7 +955,7 @@
     }
 
     /**
-     * Gets the frontend information.
+     * Gets the initialized frontend information.
      *
      * @return The frontend information. {@code null} if the operation failed.
      */
@@ -950,6 +973,16 @@
         return mFrontendInfo;
     }
 
+    /**
+     * Get a list all the existed frontend information.
+     *
+     * @return The list of all the frontend information. {@code null} if the operation failed.
+     */
+    @Nullable
+    public List<FrontendInfo> getFrontendInfoList() {
+        return Arrays.asList(getFrontendInfoListInternal());
+    }
+
     /** @hide */
     public FrontendInfo getFrontendInfoById(int id) {
         return nativeGetFrontendInfo(id);
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 2f2d8f7..82cc78d 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -25,6 +25,7 @@
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -148,7 +149,6 @@
     public static final int SUBTYPE_PTP = 16;
 
 
-
     /** @hide */
     @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER,
             STATUS_HIGH_WATER, STATUS_OVERFLOW})
@@ -180,6 +180,31 @@
      */
     public static final int STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
 
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "SCRAMBLING_STATUS_",
+            value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
+                    SCRAMBLING_STATUS_SCRAMBLED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScramblingStatusMask {}
+
+    /**
+     * Content’s scrambling status is unknown
+     */
+    public static final int SCRAMBLING_STATUS_UNKNOWN =
+            android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.UNKNOWN;
+    /**
+     * Content is not scrambled.
+     */
+    public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED =
+            android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.NOT_SCRAMBLED;
+    /**
+     * Content is scrambled.
+     */
+    public static final int SCRAMBLING_STATUS_SCRAMBLED =
+            android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.SCRAMBLED;
+
+
     private static final String TAG = "Filter";
 
     private long mNativeContext;
@@ -197,6 +222,7 @@
             int type, int subType, FilterConfiguration settings);
     private native int nativeGetId();
     private native long nativeGetId64Bit();
+    private native int nativeconfigureScramblingEvent(int scramblingStatusMask);
     private native int nativeSetDataSource(Filter source);
     private native int nativeStartFilter();
     private native int nativeStopFilter();
@@ -280,6 +306,38 @@
     }
 
     /**
+     * Configure the Filter to monitor specific Scrambling Status through
+     * {@link ScramblingStatusEvent}.
+     *
+     * <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios:
+     *
+     * <ul>
+     *   <li>When this method is called, the first detected scrambling status should be sent.
+     *   <li>When the filter transits into the monitored statuses configured in
+     *       {@code scramblingStatusMask}, event should be sent.
+     *     <ul/>
+     *
+     * <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version
+     * will cause no-op. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
+     *
+     * @param scramblingStatusMask Scrambling Statuses to be monitored. Set corresponding bit to
+     *                             monitor it. Reset to stop monitoring.
+     * @return result status of the operation.
+     */
+    @Result
+    public int configureScramblingStatusEvent(@ScramblingStatusMask int scramblingStatusMask) {
+        synchronized (mLock) {
+            TunerUtils.checkResourceState(TAG, mIsClosed);
+            if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                    TunerVersionChecker.TUNER_VERSION_1_1, "configureScramblingStatusEvent")) {
+                return Tuner.RESULT_UNAVAILABLE;
+            }
+            return nativeconfigureScramblingEvent(scramblingStatusMask);
+        }
+    }
+
+    /**
      * Sets the filter's data source.
      *
      * A filter uses demux as data source by default. If the data was packetized
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 1031e33..1bbd6e3 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -94,7 +94,7 @@
      * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
      * information.
      */
-    public int getFirstMbInSlice() {
+    public int getFirstMacroblockInSlice() {
         return mFirstMbInSlice;
     }
 
diff --git a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
new file mode 100644
index 0000000..a78b96e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Scrambling Status event sent from {@link Filter} objects with Scrambling Status type.
+ *
+ * <p>This event is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ScramblingStatusEvent extends FilterEvent {
+    private final int mScramblingStatus;
+
+    private ScramblingStatusEvent(@Filter.ScramblingStatusMask int scramblingStatus) {
+        mScramblingStatus = scramblingStatus;
+    }
+
+    /**
+     * Gets Scrambling Status Type.
+     *
+     * <p>This event field is only sent in Tuner 1.1 or higher version. Unsupported version returns
+     * default value 0. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
+     */
+    @Filter.ScramblingStatusMask
+    public int getScramblingStatus() {
+        return mScramblingStatus;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 6ea3bf8..2816199 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -99,7 +99,7 @@
      * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
      * information.
      */
-    public int getFirstMbInSlice() {
+    public int getFirstMacroblockInSlice() {
         return mFirstMbInSlice;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 68c0c96..f3e9476 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -22,6 +22,9 @@
 import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
 import android.util.Range;
 
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * This class is used to specify meta information of a frontend.
  *
@@ -118,4 +121,30 @@
     public FrontendCapabilities getFrontendCapabilities() {
         return mFrontendCap;
     }
+
+
+    /** @hide */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || !(o instanceof FrontendInfo)) {
+            return false;
+        }
+        // TODO: compare FrontendCapabilities
+        FrontendInfo info = (FrontendInfo) o;
+        return mId == info.getId() && mType == info.getType()
+                && Objects.equals(mFrequencyRange, info.getFrequencyRange())
+                && Objects.equals(mSymbolRateRange, info.getSymbolRateRange())
+                && mAcquireRange == info.getAcquireRange()
+                && mExclusiveGroupId == info.getExclusiveGroupId()
+                && Arrays.equals(mStatusCaps, info.getStatusCapabilities());
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return mId;
+    }
 }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 2541750..b9e7848 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -4024,6 +4024,28 @@
                     ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
 }
 
+static jint android_media_tv_Tuner_configure_scrambling_status_event(
+        JNIEnv* env, jobject filter, int scramblingStatusMask) {
+    sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
+    if (iFilterSp == NULL) {
+        ALOGD("Failed to configure scrambling event: filter not found");
+        return (jint) Result::NOT_INITIALIZED;
+    }
+
+    sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+    iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+    Result res;
+
+    if (iFilterSp_1_1 != NULL) {
+        res = iFilterSp_1_1->configureScramblingEvent(scramblingStatusMask);
+    } else {
+        ALOGW("configureScramblingEvent is not supported with the current HAL implementation.");
+        return (jint) Result::INVALID_STATE;
+    }
+
+    return (jint) res;
+}
+
 static jint android_media_tv_Tuner_set_filter_data_source(
         JNIEnv* env, jobject filter, jobject srcFilter) {
     sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
@@ -4695,6 +4717,8 @@
     { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
     { "nativeGetId64Bit", "()J",
             (void *)android_media_tv_Tuner_get_filter_64bit_id },
+    { "nativeconfigureScramblingEvent", "(I)I",
+            (void *)android_media_tv_Tuner_configure_scrambling_status_event },
     { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_set_filter_data_source },
     { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index c5876af..c653c73 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -87,7 +87,9 @@
     private BluetoothAdapter mBluetoothAdapter;
     private WifiManager mWifiManager;
     @Nullable private BluetoothLeScanner mBLEScanner;
-    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
+    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder()
+            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+            .build();
 
     private List<DeviceFilter<?>> mFilters;
     private List<BluetoothLeDeviceFilter> mBLEFilters;
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
index 7e7710b..0f88811 100644
--- a/packages/SettingsProvider/src/android/provider/settings/OWNERS
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 656484
 
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
 
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
index 7e7710b..0f88811 100644
--- a/packages/SettingsProvider/test/src/android/provider/OWNERS
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 656484
 
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index cc8bf4f..2cdd7f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -154,8 +154,8 @@
                         }
                         mView.resetPasswordText(true /* animate */,
                                 /* announce */
-                                result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
-                        if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+                                result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                        if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
                             mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
                             mRemainingAttempts = -1;
                             mShowDefaultMessage = true;
@@ -163,7 +163,7 @@
                                     true, KeyguardUpdateMonitor.getCurrentUser());
                         } else {
                             mShowDefaultMessage = false;
-                            if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+                            if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
                                 if (result.getAttemptsRemaining() <= 2) {
                                     // this is getting critical - show dialog
                                     getSimRemainingAttemptsDialog(
@@ -289,20 +289,14 @@
         @Override
         public void run() {
             if (DEBUG) {
-                Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
+                Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")");
             }
-            TelephonyManager telephonyManager =
-                    mTelephonyManager.createForSubscriptionId(mSubId);
-            final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
-            if (result == null) {
-                Log.e(TAG, "Error result for supplyPinReportResult.");
-                mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult()));
-            } else {
-                if (DEBUG) {
-                    Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
-                }
-                mView.post(() -> onSimCheckResponse(result));
+            TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+            final PinResult result = telephonyManager.supplyIccLockPin(mPin);
+            if (DEBUG) {
+                Log.v(TAG, "supplyIccLockPin returned: " + result.toString());
             }
+            mView.post(() -> onSimCheckResponse(result));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index a873749..adb4c13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -267,8 +267,8 @@
                         }
                         mView.resetPasswordText(true /* animate */,
                                 /* announce */
-                                result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
-                        if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+                                result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                        if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
                             mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
                             mRemainingAttempts = -1;
                             mShowDefaultMessage = true;
@@ -277,7 +277,7 @@
                                     true, KeyguardUpdateMonitor.getCurrentUser());
                         } else {
                             mShowDefaultMessage = false;
-                            if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+                            if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
                                 // show message
                                 mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
                                         result.getAttemptsRemaining(), false,
@@ -390,23 +390,15 @@
 
         @Override
         public void run() {
-            if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
-            TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
-            final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
-            if (result == null) {
-                Log.e(TAG, "Error result for supplyPukReportResult.");
-                mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult()));
-            } else {
-                if (DEBUG) {
-                    Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
-                }
-                mView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onSimLockChangedResponse(result);
-                    }
-                });
+            if (DEBUG) {
+                Log.v(TAG, "call supplyIccLockPuk(subid=" + mSubId + ")");
             }
+            TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+            final PinResult result = telephonyManager.supplyIccLockPuk(mPuk, mPin);
+            if (DEBUG) {
+                Log.v(TAG, "supplyIccLockPuk returned: " + result.toString());
+            }
+            mView.post(() -> onSimLockChangedResponse(result));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f073ced..7e48edf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -109,6 +109,7 @@
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
                     .setBubbles(mWMComponent.getBubbles())
+                    .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
                     .setShellDump(mWMComponent.getShellDump());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components.
@@ -116,6 +117,7 @@
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
                     .setBubbles(Optional.ofNullable(null))
+                    .setHideDisplayCutout(Optional.ofNullable(null))
                     .setShellDump(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index b3e6033..7127f26 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -23,6 +23,7 @@
 import android.app.INotificationManager;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.om.OverlayManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -43,6 +44,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Prefs;
+import com.android.systemui.R;
 import com.android.systemui.accessibility.ModeSwitchesController;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
@@ -78,6 +80,7 @@
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.theme.ThemeOverlayApplier;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -194,6 +197,17 @@
     }
 
     /** */
+    @SysUISingleton
+    @Provides
+    static ThemeOverlayApplier provideThemeOverlayManager(Context context,
+            @Background Executor bgExecutor, OverlayManager overlayManager,
+            DumpManager dumpManager) {
+        return new ThemeOverlayApplier(overlayManager, bgExecutor,
+                context.getString(R.string.launcher_overlayable_package),
+                context.getString(R.string.themepicker_overlayable_package), dumpManager);
+    }
+
+    /** */
     @Provides
     @SysUISingleton
     public NavigationBarController provideNavigationBarController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 9705551..c3f2e18 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -31,6 +31,7 @@
 import android.app.trust.TrustManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.om.OverlayManager;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -351,7 +352,7 @@
 
     @Provides
     static WallpaperManager provideWallpaperManager(Context context) {
-        return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
+        return context.getSystemService(WallpaperManager.class);
     }
 
     @Provides
@@ -363,6 +364,12 @@
 
     @Provides
     @Singleton
+    static OverlayManager provideOverlayManager(Context context) {
+        return context.getSystemService(OverlayManager.class);
+    }
+
+    @Provides
+    @Singleton
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 54aeab5..02c3c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -26,6 +26,7 @@
 import com.android.systemui.util.InjectionInflationController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -66,6 +67,9 @@
         Builder setBubbles(Optional<Bubbles> b);
 
         @BindsInstance
+        Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
+
+        @BindsInstance
         Builder setShellDump(Optional<ShellDump> shellDump);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 9154ddb..b3e62e52 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -20,6 +20,7 @@
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -71,4 +72,7 @@
 
     @WMSingleton
     Optional<Bubbles> getBubbles();
+
+    @WMSingleton
+    Optional<HideDisplayCutout> getHideDisplayCutout();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 07a5839..35874cd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -154,7 +154,6 @@
     private SaveImageInBackgroundTask mSaveInBgTask;
 
     private Animator mScreenshotAnimation;
-    private Animator mDismissAnimation;
 
     private Runnable mOnCompleteRunnable;
     private boolean mInDarkMode;
@@ -168,7 +167,6 @@
                 case MESSAGE_CORNER_TIMEOUT:
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
                     ScreenshotController.this.dismissScreenshot(false);
-                    mOnCompleteRunnable.run();
                     break;
                 default:
                     break;
@@ -279,30 +277,22 @@
                 rect -> takeScreenshotInternal(finisher, rect));
     }
 
-    boolean isDismissing() {
-        return (mDismissAnimation != null && mDismissAnimation.isRunning());
-    }
-
     /**
      * Clears current screenshot
      */
     void dismissScreenshot(boolean immediate) {
-        Log.v(TAG, "clearing screenshot");
+        // If we're already animating out, don't restart the animation
+        // (but do obey an immediate dismissal)
+        if (!immediate && mScreenshotView.isDismissing()) {
+            Log.v(TAG, "Already dismissing, ignoring duplicate command");
+            return;
+        }
+        Log.v(TAG, "Clearing screenshot");
         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-        mScreenshotView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
-                mScreenshotView);
-        if (!immediate) {
-            mDismissAnimation = mScreenshotView.createScreenshotDismissAnimation();
-            mDismissAnimation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    clearScreenshot();
-                }
-            });
-            mDismissAnimation.start();
+        if (immediate) {
+            resetScreenshotView();
         } else {
-            clearScreenshot();
+            mScreenshotView.animateDismissal();
         }
     }
 
@@ -373,6 +363,7 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+        mScreenshotView.init(mUiEventLogger, this::resetScreenshotView);
 
         // TODO(159460485): Remove this when focus is handled properly in the system
         mScreenshotView.setOnTouchListener((v, event) -> {
@@ -438,10 +429,10 @@
 
         if (mScreenshotView.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
-            if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
+            if (!mScreenshotView.isDismissing()) {
                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
             }
-            dismissScreenshot(true);
+            mScreenshotView.reset();
         }
 
         mScreenBitmap = screenshot;
@@ -459,10 +450,6 @@
 
         onConfigChanged(mContext.getResources().getConfiguration());
 
-        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
-            mDismissAnimation.cancel();
-        }
-
         // The window is focusable by default
         setWindowFocusable(true);
 
@@ -474,7 +461,8 @@
             mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
                     mScreenshotView.showScrollChip(() ->
                             runScrollCapture(connection,
-                                    () -> dismissScreenshot(true))));
+                                    () -> mScreenshotHandler.post(
+                                            () -> dismissScreenshot(false)))));
         }
     }
 
@@ -519,6 +507,7 @@
      */
     private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
             boolean showFlash) {
+        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
         mScreenshotHandler.post(() -> {
             if (!mScreenshotView.isAttachedToWindow()) {
                 mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
@@ -531,8 +520,7 @@
                         mScreenshotView);
 
                 mScreenshotAnimation =
-                        mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash,
-                                this::onElementTapped);
+                        mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
 
                 saveScreenshotInWorkerThread(finisher,
                         new ScreenshotController.ActionsReadyListener() {
@@ -551,6 +539,14 @@
         });
     }
 
+    private void resetScreenshotView() {
+        if (mScreenshotView.isAttachedToWindow()) {
+            mWindowManager.removeView(mScreenshotView);
+        }
+        mScreenshotView.reset();
+        mOnCompleteRunnable.run();
+    }
+
     /**
      * Creates a new worker thread and saves the screenshot to the media store.
      */
@@ -590,7 +586,6 @@
                 SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
 
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
         mScreenshotHandler.sendMessageDelayed(
                 mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
                 timeoutMs);
@@ -602,24 +597,16 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             super.onAnimationEnd(animation);
-                            mScreenshotView.setChipIntents(
-                                    imageData, event -> onElementTapped(event));
+                            mScreenshotView.setChipIntents(imageData);
                         }
                     });
                 } else {
-                    mScreenshotView.setChipIntents(
-                            imageData, this::onElementTapped);
+                    mScreenshotView.setChipIntents(imageData);
                 }
             });
         }
     }
 
-    private void onElementTapped(ScreenshotEvent event) {
-        mUiEventLogger.log(event);
-        dismissScreenshot(false);
-        mOnCompleteRunnable.run();
-    }
-
     /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
@@ -633,20 +620,11 @@
         }
     }
 
-    private void clearScreenshot() {
-        if (mScreenshotView.isAttachedToWindow()) {
-            mWindowManager.removeView(mScreenshotView);
-        }
-
-        mScreenshotView.reset();
-    }
-
     private boolean isUserSetupComplete() {
         return Settings.Secure.getInt(mContext.getContentResolver(),
                 SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
     }
 
-
     /**
      * Updates the window focusability.  If the window is already showing, then it updates the
      * window immediately, otherwise the layout params will be applied when the window is next
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 3383f80..2b4eacb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -60,6 +60,7 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -115,6 +116,10 @@
     private ScreenshotActionChip mEditChip;
     private ScreenshotActionChip mScrollChip;
 
+    private UiEventLogger mUiEventLogger;
+    private Runnable mOnDismissRunnable;
+    private Animator mDismissAnimation;
+
     private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
     private PendingInteraction mPendingInteraction;
 
@@ -161,7 +166,7 @@
     public void showScrollChip(Runnable onClick) {
         mScrollChip.setVisibility(VISIBLE);
         mScrollChip.setOnClickListener((v) ->
-                onClick.run()
+                        onClick.run()
                 // TODO Logging, store event consumer to a field
                 //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
         );
@@ -248,6 +253,17 @@
         requestFocus();
     }
 
+    /**
+     * Set up the logger and callback on dismissal.
+     *
+     * Note: must be called before any other (non-constructor) method or null pointer exceptions
+     * may occur.
+     */
+    void init(UiEventLogger uiEventLogger, Runnable onDismissRunnable) {
+        mUiEventLogger = uiEventLogger;
+        mOnDismissRunnable = onDismissRunnable;
+    }
+
     void takePartialScreenshot(Consumer<Rect> onPartialScreenshotSelected) {
         mScreenshotSelectorView.setOnScreenshotSelected(onPartialScreenshotSelected);
         mScreenshotSelectorView.setVisibility(View.VISIBLE);
@@ -260,8 +276,7 @@
         mScreenshotPreview.setVisibility(View.INVISIBLE);
     }
 
-    AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash,
-            Consumer<ScreenshotEvent> onElementTapped) {
+    AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
         mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         mScreenshotPreview.buildLayer();
 
@@ -362,8 +377,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mDismissButton.setOnClickListener(view ->
-                        onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL));
+                mDismissButton.setOnClickListener(view -> {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+                    animateDismissal();
+                });
                 mDismissButton.setAlpha(1);
                 float dismissOffset = mDismissButton.getWidth() / 2f;
                 float finalDismissX = mDirectionLTR
@@ -460,19 +477,25 @@
         return animator;
     }
 
-    void setChipIntents(ScreenshotController.SavedImageData imageData,
-            Consumer<ScreenshotEvent> onElementTapped) {
+    void setChipIntents(ScreenshotController.SavedImageData imageData) {
         mShareChip.setPendingIntent(imageData.shareAction.actionIntent,
-                () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED));
+                () -> {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
+                    animateDismissal();
+                });
         mEditChip.setPendingIntent(imageData.editAction.actionIntent,
-                () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED));
+                () -> {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
+                    animateDismissal();
+                });
         mScreenshotPreview.setOnClickListener(v -> {
             try {
                 imageData.editAction.actionIntent.send();
             } catch (PendingIntent.CanceledException e) {
                 Log.e(TAG, "Intent cancelled", e);
             }
-            onElementTapped.accept(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+            animateDismissal();
         });
 
         if (mPendingInteraction != null) {
@@ -496,43 +519,49 @@
                 actionChip.setText(smartAction.title);
                 actionChip.setIcon(smartAction.getIcon(), false);
                 actionChip.setPendingIntent(smartAction.actionIntent,
-                        () -> onElementTapped.accept(
-                                ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED));
+                        () -> {
+                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
+                            animateDismissal();
+                        });
                 mActionsView.addView(actionChip);
                 mSmartChips.add(actionChip);
             }
         }
     }
 
+    boolean isDismissing() {
+        return (mDismissAnimation != null && mDismissAnimation.isRunning());
+    }
 
-    AnimatorSet createScreenshotDismissAnimation() {
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
-        alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
-        alphaAnim.addUpdateListener(animation -> {
-            setAlpha(1 - animation.getAnimatedFraction());
+    void animateDismissal() {
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        mDismissAnimation = createScreenshotDismissAnimation();
+        mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled = false;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (!mCancelled) {
+                    mOnDismissRunnable.run();
+                }
+            }
         });
-
-        ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
-        yAnim.setInterpolator(mAccelerateInterpolator);
-        yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
-        float screenshotStartY = mScreenshotPreview.getTranslationY();
-        float dismissStartY = mDismissButton.getTranslationY();
-        yAnim.addUpdateListener(animation -> {
-            float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
-            mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
-            mDismissButton.setTranslationY(dismissStartY + yDelta);
-            mActionsContainer.setTranslationY(yDelta);
-            mActionsContainerBackground.setTranslationY(yDelta);
-        });
-
-        AnimatorSet animSet = new AnimatorSet();
-        animSet.play(yAnim).with(alphaAnim);
-
-        return animSet;
+        mDismissAnimation.start();
     }
 
     void reset() {
+        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+            mDismissAnimation.cancel();
+        }
+        // Make sure we clean up the view tree observer
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         // Clear any references to the bitmap
         mScreenshotPreview.setImageDrawable(null);
         mActionsContainerBackground.setVisibility(View.GONE);
@@ -561,6 +590,33 @@
         mScreenshotSelectorView.stop();
     }
 
+    private AnimatorSet createScreenshotDismissAnimation() {
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
+        alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
+        alphaAnim.addUpdateListener(animation -> {
+            setAlpha(1 - animation.getAnimatedFraction());
+        });
+
+        ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
+        yAnim.setInterpolator(mAccelerateInterpolator);
+        yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
+        float screenshotStartY = mScreenshotPreview.getTranslationY();
+        float dismissStartY = mDismissButton.getTranslationY();
+        yAnim.addUpdateListener(animation -> {
+            float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
+            mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
+            mDismissButton.setTranslationY(dismissStartY + yDelta);
+            mActionsContainer.setTranslationY(yDelta);
+            mActionsContainerBackground.setTranslationY(yDelta);
+        });
+
+        AnimatorSet animSet = new AnimatorSet();
+        animSet.play(yAnim).with(alphaAnim);
+
+        return animSet;
+    }
+
     /**
      * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 4e22833..5f35e60 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -144,7 +144,7 @@
 
     @Override
     public boolean onUnbind(Intent intent) {
-        if (mScreenshot != null && !mScreenshot.isDismissing()) {
+        if (mScreenshot != null) {
             mScreenshot.dismissScreenshot(true);
         }
         unregisterReceiver(mBroadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
rename to packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 665cb63..0aa2a73 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -21,11 +21,18 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -34,9 +41,19 @@
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
-class ThemeOverlayManager {
+/**
+ * Responsible for orchestrating overlays, based on user preferences and other inputs from
+ * {@link ThemeOverlayController}.
+ */
+@SysUISingleton
+public class ThemeOverlayApplier implements Dumpable {
     private static final String TAG = "ThemeOverlayManager";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @VisibleForTesting
+    static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
+    @VisibleForTesting
+    static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
 
     @VisibleForTesting
     static final String ANDROID_PACKAGE = "android";
@@ -46,7 +63,11 @@
     static final String SYSUI_PACKAGE = "com.android.systemui";
 
     @VisibleForTesting
-    static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color";
+    static final String OVERLAY_CATEGORY_ACCENT_COLOR =
+            "android.theme.customization.accent_color";
+    @VisibleForTesting
+    static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
+            "android.theme.customization.system_palette";
     @VisibleForTesting
     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
     @VisibleForTesting
@@ -73,24 +94,36 @@
      * starts with launcher and grouped by target package.
      */
     static final List<String> THEME_CATEGORIES = Lists.newArrayList(
+            OVERLAY_CATEGORY_SYSTEM_PALETTE,
             OVERLAY_CATEGORY_ICON_LAUNCHER,
             OVERLAY_CATEGORY_SHAPE,
             OVERLAY_CATEGORY_FONT,
-            OVERLAY_CATEGORY_COLOR,
+            OVERLAY_CATEGORY_ACCENT_COLOR,
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI,
             OVERLAY_CATEGORY_ICON_SETTINGS,
             OVERLAY_CATEGORY_ICON_THEME_PICKER);
 
-    /* Categories that need to applied to the current user as well as the system user. */
+    /* Categories that need to be applied to the current user as well as the system user. */
     @VisibleForTesting
     static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
-            OVERLAY_CATEGORY_COLOR,
+            OVERLAY_CATEGORY_SYSTEM_PALETTE,
+            OVERLAY_CATEGORY_ACCENT_COLOR,
             OVERLAY_CATEGORY_FONT,
             OVERLAY_CATEGORY_SHAPE,
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI);
 
+    /**
+     * List of main colors of Monet themes. These are extracted from overlays installed
+     * on the system.
+     */
+    private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
+    /**
+     * Same as above, but providing accent colors instead of a system palette.
+     */
+    private final ArrayList<Integer> mAccentColors = new ArrayList<>();
+
     /* Allowed overlay categories for each target package. */
     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
     /* Target package for each overlay category. */
@@ -100,15 +133,15 @@
     private final String mLauncherPackage;
     private final String mThemePickerPackage;
 
-    ThemeOverlayManager(OverlayManager overlayManager, Executor executor,
-            String launcherPackage, String themePickerPackage) {
+    public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+            String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
         mOverlayManager = overlayManager;
         mExecutor = executor;
         mLauncherPackage = launcherPackage;
         mThemePickerPackage = themePickerPackage;
         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
-                OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT,
-                OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID));
+                OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+                OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID));
         mTargetPackageToCategories.put(SYSUI_PACKAGE,
                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
         mTargetPackageToCategories.put(SETTINGS_PACKAGE,
@@ -117,7 +150,7 @@
                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER));
         mTargetPackageToCategories.put(mThemePickerPackage,
                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER));
-        mCategoryToTargetPackage.put(OVERLAY_CATEGORY_COLOR, ANDROID_PACKAGE);
+        mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE);
@@ -125,6 +158,54 @@
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
+
+        collectMonetSystemOverlays();
+        dumpManager.registerDumpable(TAG, this);
+    }
+
+    /**
+     * List of accent colors available as Monet overlays.
+     */
+    List<Integer> getAvailableAccentColors() {
+        return mAccentColors;
+    }
+
+    /**
+     * List of main system colors available as Monet overlays.
+     */
+    List<Integer> getAvailableSystemColors() {
+        return mMainSystemColors;
+    }
+
+    private void collectMonetSystemOverlays() {
+        List<OverlayInfo> androidOverlays = mOverlayManager
+                .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
+        for (OverlayInfo overlayInfo : androidOverlays) {
+            String packageName = overlayInfo.packageName;
+            if (DEBUG) {
+                Log.d(TAG, "Processing overlay " + packageName);
+            }
+            if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
+                    && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
+                try {
+                    String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
+                    mMainSystemColors.add(Integer.parseInt(color, 16));
+                } catch (NumberFormatException e) {
+                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
+                }
+            } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
+                    && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
+                try {
+                    String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
+                    mAccentColors.add(Integer.parseInt(color, 16));
+                } catch (NumberFormatException e) {
+                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
+                }
+            } else if (DEBUG) {
+                Log.d(TAG, "Unknown overlay: " + packageName + " category: "
+                        + overlayInfo.category);
+            }
+        }
     }
 
     /**
@@ -184,4 +265,13 @@
             }
         });
     }
+
+    /**
+     * @inherit
+     */
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mMainSystemColors=" + mMainSystemColors.size());
+        pw.println("mAccentColors=" + mAccentColors.size());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 132e092..c4e2b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -15,16 +15,20 @@
  */
 package com.android.systemui.theme;
 
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+
 import android.app.ActivityManager;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.om.OverlayManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.graphics.Color;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -33,20 +37,32 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
-import com.android.systemui.R;
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.android.collect.Sets;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -60,49 +76,76 @@
  * associated work profiles
  */
 @SysUISingleton
-public class ThemeOverlayController extends SystemUI {
+public class ThemeOverlayController extends SystemUI implements Dumpable {
     private static final String TAG = "ThemeOverlayController";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private ThemeOverlayManager mThemeManager;
-    private UserManager mUserManager;
-    private BroadcastDispatcher mBroadcastDispatcher;
+    // If lock screen wallpaper colors should also be considered when selecting the theme.
+    // Doing this has performance impact, given that overlays would need to be swapped when
+    // the device unlocks.
+    @VisibleForTesting
+    static final boolean USE_LOCK_SCREEN_WALLPAPER = false;
+
+    private final ThemeOverlayApplier mThemeManager;
+    private final UserManager mUserManager;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final Executor mBgExecutor;
+    private final SecureSettings mSecureSettings;
+    private final Executor mMainExecutor;
     private final Handler mBgHandler;
+    private final WallpaperManager mWallpaperManager;
+    private final KeyguardStateController mKeyguardStateController;
+    private WallpaperColors mLockColors;
+    private WallpaperColors mSystemColors;
+    // Color extracted from wallpaper, NOT the color used on the overlay
+    private int mMainWallpaperColor = Color.TRANSPARENT;
+    // Color extracted from wallpaper, NOT the color used on the overlay
+    private int mWallpaperAccentColor = Color.TRANSPARENT;
+    // Main system color that maps to an overlay color
+    private int mSystemOverlayColor = Color.TRANSPARENT;
+    // Accent color that maps to an overlay color
+    private int mAccentOverlayColor = Color.TRANSPARENT;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
-            @Background Handler bgHandler) {
+            @Background Handler bgHandler, @Main Executor mainExecutor,
+            @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
+            SecureSettings secureSettings, WallpaperManager wallpaperManager,
+            UserManager userManager, KeyguardStateController keyguardStateController,
+            DumpManager dumpManager) {
         super(context);
+
         mBroadcastDispatcher = broadcastDispatcher;
+        mUserManager = userManager;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
         mBgHandler = bgHandler;
+        mThemeManager = themeOverlayApplier;
+        mSecureSettings = secureSettings;
+        mWallpaperManager = wallpaperManager;
+        mKeyguardStateController = keyguardStateController;
+        dumpManager.registerDumpable(TAG, this);
     }
 
     @Override
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
-        mUserManager = mContext.getSystemService(UserManager.class);
-        mThemeManager = new ThemeOverlayManager(
-                mContext.getSystemService(OverlayManager.class),
-                AsyncTask.THREAD_POOL_EXECUTOR,
-                mContext.getString(R.string.launcher_overlayable_package),
-                mContext.getString(R.string.themepicker_overlayable_package));
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
+        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
                 updateThemeOverlays();
             }
-        }, filter, mBgHandler, UserHandle.ALL);
-        mContext.getContentResolver().registerContentObserver(
+        }, filter, mBgExecutor, UserHandle.ALL);
+        mSecureSettings.registerContentObserverForUser(
                 Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
                 false,
                 new ContentObserver(mBgHandler) {
-
                     @Override
-                    public void onChange(boolean selfChange, Collection<Uri> uris, int flags,
+                    public void onChange(boolean selfChange, Collection<Uri> collection, int flags,
                             int userId) {
                         if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId);
                         if (ActivityManager.getCurrentUser() == userId) {
@@ -111,20 +154,147 @@
                     }
                 },
                 UserHandle.USER_ALL);
+
+        // Upon boot, make sure we have the most up to date colors
+        mBgExecutor.execute(() -> {
+            WallpaperColors lockColors = mWallpaperManager.getWallpaperColors(
+                    WallpaperManager.FLAG_LOCK);
+            WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
+                    WallpaperManager.FLAG_SYSTEM);
+            mMainExecutor.execute(() -> {
+                if (USE_LOCK_SCREEN_WALLPAPER) {
+                    mLockColors = lockColors;
+                }
+                mSystemColors = systemColor;
+                reevaluateSystemTheme();
+            });
+        });
+        if (USE_LOCK_SCREEN_WALLPAPER) {
+            mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardShowingChanged() {
+                    if (mLockColors == null) {
+                        return;
+                    }
+                    // It's possible that the user has a lock screen wallpaper. On this case we'll
+                    // end up with different colors after unlocking.
+                    reevaluateSystemTheme();
+                }
+            });
+        }
+        mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> {
+            if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) {
+                mLockColors = wallpaperColors;
+                if (DEBUG) {
+                    Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
+                }
+            }
+            if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+                mSystemColors = wallpaperColors;
+                if (DEBUG) {
+                    Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
+                }
+            }
+            reevaluateSystemTheme();
+        }, null, UserHandle.USER_ALL);
+    }
+
+    private void reevaluateSystemTheme() {
+        if (mLockColors == null && mSystemColors == null) {
+            Log.w(TAG, "Cannot update theme, colors are null");
+            return;
+        }
+        WallpaperColors currentColor =
+                mKeyguardStateController.isShowing() && mLockColors != null
+                        ? mLockColors : mSystemColors;
+        int mainColor = currentColor.getPrimaryColor().toArgb();
+
+        //TODO(b/172860591) implement more complex logic for picking accent color.
+        //For now, picking the secondary should be enough.
+        Color accentCandidate = currentColor.getSecondaryColor();
+        if (accentCandidate == null) {
+            accentCandidate = currentColor.getTertiaryColor();
+        }
+        if (accentCandidate == null) {
+            accentCandidate = currentColor.getPrimaryColor();
+        }
+
+        if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate.toArgb()) {
+            return;
+        }
+
+        mMainWallpaperColor = mainColor;
+        mWallpaperAccentColor = accentCandidate.toArgb();
+
+        // Let's compare these colors to our finite set of overlays, and then pick an overlay.
+        List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
+        List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
+
+        if (systemColors.size() == 0 || accentColors.size() == 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+            }
+            return;
+        }
+
+        mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
+        mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
+
+        updateThemeOverlays();
+    }
+
+    /**
+     * Given a color and a list of candidates, return the candidate that's the most similar to the
+     * given color.
+     */
+    private static int getClosest(List<Integer> candidates, int color) {
+        float[] hslMain = new float[3];
+        float[] hslCandidate = new float[3];
+
+        ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
+        hslMain[0] /= 360f;
+
+        float minDistance = Float.MAX_VALUE;
+        int closestColor = Color.TRANSPARENT;
+        for (int candidate: candidates) {
+            ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
+                    hslCandidate);
+            hslCandidate[0] /= 360f;
+
+            float sqDistance = squared(hslCandidate[0] - hslMain[0])
+                    + squared(hslCandidate[1] - hslMain[1])
+                    + squared(hslCandidate[2] - hslMain[2]);
+            if (sqDistance < minDistance) {
+                minDistance = sqDistance;
+                closestColor = candidate;
+            }
+        }
+        return closestColor;
+    }
+
+    private static float squared(float f) {
+        return f * f;
     }
 
     private void updateThemeOverlays() {
         final int currentUser = ActivityManager.getCurrentUser();
-        final String overlayPackageJson = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(), Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+        final String overlayPackageJson = mSecureSettings.getStringForUser(
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 currentUser);
         if (DEBUG) Log.d(TAG, "updateThemeOverlays: " + overlayPackageJson);
+        boolean hasSystemPalette = false;
+        boolean hasAccentColor = false;
         final Map<String, String> categoryToPackage = new ArrayMap<>();
         if (!TextUtils.isEmpty(overlayPackageJson)) {
             try {
                 JSONObject object = new JSONObject(overlayPackageJson);
-                for (String category : ThemeOverlayManager.THEME_CATEGORIES) {
+                for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
                     if (object.has(category)) {
+                        if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
+                            hasAccentColor = true;
+                        } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
+                            hasSystemPalette = true;
+                        }
                         categoryToPackage.put(category, object.getString(category));
                     }
                 }
@@ -132,6 +302,20 @@
                 Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
             }
         }
+
+        // Let's apply the system palette, but only if it was not overridden by the style picker.
+        if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
+                    ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
+                            + Integer.toHexString(mSystemOverlayColor).toUpperCase());
+        }
+        // Same for the accent color
+        if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
+                    ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
+                            + Integer.toHexString(mAccentOverlayColor).toUpperCase());
+        }
+
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
         for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) {
             if (userInfo.isManagedProfile()) {
@@ -140,4 +324,14 @@
         }
         mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
     }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mLockColors=" + mLockColors);
+        pw.println("mSystemColors=" + mSystemColors);
+        pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
+        pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
+        pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
+        pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7a24438..04f1f86 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.wmshell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
@@ -29,21 +27,14 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,8 +46,6 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -64,6 +53,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEvents;
@@ -105,6 +95,7 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
     private final ProtoTracer mProtoTracer;
     private final Optional<ShellDump> mShellDump;
 
@@ -123,6 +114,7 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutoutOptional,
             ProtoTracer protoTracer,
             Optional<ShellDump> shellDump) {
         super(context);
@@ -135,6 +127,7 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
+        mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
         mProtoTracer = protoTracer;
         mProtoTracer.add(this);
         mShellDump = shellDump;
@@ -146,6 +139,7 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
+        mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
     }
 
     @VisibleForTesting
@@ -284,6 +278,16 @@
         });
     }
 
+    @VisibleForTesting
+    void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) {
+        mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+            @Override
+            public void onConfigChanged(Configuration newConfig) {
+                hideDisplayCutout.onConfigurationChanged(newConfig);
+            }
+        });
+    }
+
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
         if (proto.wmShell == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index a197789..8c2980f 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -28,7 +28,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,6 +45,8 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -90,9 +91,10 @@
     static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer,
             Optional<SplitScreen> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional) {
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout) {
         return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
-                oneHandedOptional));
+                oneHandedOptional, hideDisplayCutout));
     }
 
     @WMSingleton
@@ -215,4 +217,10 @@
         return new HandlerExecutor(handler);
     }
 
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController) {
+        return Optional.ofNullable(HideDisplayCutoutController.create(context, displayController));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index c99deb6..edaff5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -15,22 +15,24 @@
  */
 package com.android.systemui.theme;
 
-import static com.android.systemui.theme.ThemeOverlayManager.ANDROID_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_COLOR;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_FONT;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_ANDROID;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_LAUNCHER;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SETTINGS;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SYSUI;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_THEME_PICKER;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_SHAPE;
-import static com.android.systemui.theme.ThemeOverlayManager.SETTINGS_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.SYSTEM_USER_CATEGORIES;
-import static com.android.systemui.theme.ThemeOverlayManager.SYSUI_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.THEME_CATEGORIES;
+import static com.android.systemui.theme.ThemeOverlayApplier.ANDROID_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_FONT;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_LAUNCHER;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.SYSTEM_USER_CATEGORIES;
+import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -44,6 +46,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 
 import com.google.android.collect.Maps;
 import com.google.common.collect.Lists;
@@ -63,7 +66,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class ThemeOverlayManagerTest extends SysuiTestCase {
+public class ThemeOverlayApplierTest extends SysuiTestCase {
     private static final String TEST_DISABLED_PREFIX = "com.example.";
     private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
 
@@ -82,26 +85,32 @@
 
     @Mock
     OverlayManager mOverlayManager;
+    @Mock
+    DumpManager mDumpManager;
 
-    private ThemeOverlayManager mManager;
+    private ThemeOverlayApplier mManager;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mManager = new ThemeOverlayManager(mOverlayManager, MoreExecutors.directExecutor(),
-                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE);
+        mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
         when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
                 .thenReturn(Lists.newArrayList(
-                        createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_COLOR,
-                                ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, false),
+                        createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
+                        createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, false),
-                        createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_COLOR,
-                                ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, true),
+                        createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
+                        createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
@@ -132,6 +141,8 @@
                                 THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER,
                                 THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true)));
+        clearInvocations(mOverlayManager);
+        verify(mDumpManager).registerDumpable(any(), any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
new file mode 100644
index 0000000..aee8840
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.theme;
+
+import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ThemeOverlayControllerTest extends SysuiTestCase {
+
+    private ThemeOverlayController mThemeOverlayController;
+    @Mock
+    private Executor mBgExecutor;
+    @Mock
+    private Executor mMainExecutor;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private Handler mBgHandler;
+    @Mock
+    private ThemeOverlayApplier mThemeOverlayApplier;
+    @Mock
+    private SecureSettings mSecureSettings;
+    @Mock
+    private WallpaperManager mWallpaperManager;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private DumpManager mDumpManager;
+    @Captor
+    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
+    @Captor
+    private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> mColorsListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mThemeOverlayController = new ThemeOverlayController(null /* context */,
+                mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
+                mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
+                mDumpManager);
+
+        mThemeOverlayController.start();
+        if (USE_LOCK_SCREEN_WALLPAPER) {
+            verify(mKeyguardStateController).addCallback(
+                    mKeyguardStateControllerCallback.capture());
+        }
+        verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
+                eq(UserHandle.USER_ALL));
+        verify(mDumpManager).registerDumpable(any(), any());
+
+        List<Integer> colorList = List.of(Color.RED, Color.BLUE);
+        when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
+        when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
+    }
+
+    @Test
+    public void start_checksWallpaper() {
+        ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+        verify(mBgExecutor).execute(registrationRunnable.capture());
+
+        registrationRunnable.getValue().run();
+        verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK));
+        verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_setsTheme() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier).getAvailableSystemColors();
+        verify(mThemeOverlayApplier).getAvailableAccentColors();
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE
+                        + Integer.toHexString(Color.RED).toUpperCase());
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
+                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE
+                        + Integer.toHexString(Color.BLUE).toUpperCase());
+
+        // Should not ask again if changed to same value
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        verifyNoMoreInteractions(mThemeOverlayApplier);
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.system_palette\":\"override.package.name\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier).getAvailableSystemColors();
+        verify(mThemeOverlayApplier).getAvailableAccentColors();
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo("override.package.name");
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a46e563..230aeab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -45,9 +45,6 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.pm.LauncherApps;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
 import android.os.Handler;
@@ -99,7 +96,7 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -206,8 +203,8 @@
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
     @Mock
     private BubbleLogger mBubbleLogger;
-    @Mock
-    private BubblePositioner mPositioner;
+
+    private TestableBubblePositioner mPositioner;
 
     private BubbleData mBubbleData;
 
@@ -255,12 +252,8 @@
                 mSysUiStateBubblesExpanded =
                         (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
 
-        mBubbleData = new BubbleData(mContext, mBubbleLogger);
-
-        Rect availableRect = new Rect(0, 0, 1000, 5000);
-        when(mPositioner.getAvailableRect()).thenReturn(availableRect);
-        when(mPositioner.getOrientation()).thenReturn(Configuration.ORIENTATION_PORTRAIT);
-        when(mPositioner.getInsets()).thenReturn(Insets.of(0, 0, 0, 0));
+        mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
 
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -585,7 +578,7 @@
     }
 
     @Test
-    public void testRemoveLastExpandedCollapses() {
+    public void testRemoveLastExpanded_selectsOverflow() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mEntryListener.onPendingEntryAdded(mRow2.getEntry());
@@ -625,10 +618,38 @@
                         stackView.getExpandedBubble().getKey()).getKey(),
                 Bubbles.DISMISS_USER_GESTURE);
 
-        // Make sure state changes and collapse happens
+        // Overflow should be selected
+        assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
+        assertTrue(mBubbleController.hasBubbles());
+        assertTrue(mSysUiStateBubblesExpanded);
+    }
+
+    @Test
+    public void testRemoveLastExpandedEmptyOverflow_collapses() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        // Expand
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+
+        assertTrue(mSysUiStateBubblesExpanded);
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Block the bubble so it won't be in the overflow
+        mBubbleController.removeBubble(
+                mBubbleData.getBubbleInStackWithKey(
+                        stackView.getExpandedBubble().getKey()).getKey(),
+                Bubbles.DISMISS_BLOCKED);
+
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+
+        // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
         assertFalse(mBubbleController.hasBubbles());
-
         assertFalse(mSysUiStateBubblesExpanded);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index d8033db..bbcc30a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -42,9 +42,6 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.pm.LauncherApps;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -92,7 +89,7 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -188,8 +185,8 @@
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
     @Mock
     private BubbleLogger mBubbleLogger;
-    @Mock
-    private BubblePositioner mPositioner;
+
+    private TestableBubblePositioner mPositioner;
 
     private BubbleData mBubbleData;
 
@@ -224,12 +221,8 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
-        mBubbleData = new BubbleData(mContext, mBubbleLogger);
-
-        Rect availableRect = new Rect(0, 0, 1000, 5000);
-        when(mPositioner.getAvailableRect()).thenReturn(availableRect);
-        when(mPositioner.getOrientation()).thenReturn(Configuration.ORIENTATION_PORTRAIT);
-        when(mPositioner.getInsets()).thenReturn(Insets.of(0, 0, 0, 0));
+        mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
 
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -516,7 +509,7 @@
     }
 
     @Test
-    public void testRemoveLastExpandedCollapses() {
+    public void testRemoveLastExpanded_selectsOverflow() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onEntryAdded(mRow.getEntry());
         mEntryListener.onEntryAdded(mRow2.getEntry());
@@ -554,11 +547,39 @@
                         stackView.getExpandedBubble().getKey()).getKey(),
                 Bubbles.DISMISS_USER_GESTURE);
 
-        // Make sure state changes and collapse happens
+        // Overflow should be selected
+        assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
+        assertTrue(mBubbleController.hasBubbles());
+    }
+
+    @Test
+    public void testRemoveLastExpandedEmptyOverflow_collapses() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        // Expand
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+        // Block the bubble so it won't be in the overflow
+        mBubbleController.removeBubble(
+                mBubbleData.getBubbleInStackWithKey(
+                        stackView.getExpandedBubble().getKey()).getKey(),
+                Bubbles.DISMISS_BLOCKED);
+
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+
+        // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
         assertFalse(mBubbleController.hasBubbles());
     }
 
+
     @Test
     public void testAutoExpand_fails_noFlag() {
         assertFalse(mBubbleController.isStackExpanded());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
new file mode 100644
index 0000000..24a7cd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+import com.android.wm.shell.bubbles.BubblePositioner;
+
+public class TestableBubblePositioner extends BubblePositioner {
+
+    public TestableBubblePositioner(Context context,
+            WindowManager windowManager) {
+        super(context, windowManager);
+
+        updateInternal(Configuration.ORIENTATION_PORTRAIT,
+                Insets.of(0, 0, 0, 0),
+                new Rect(0, 0, 500, 1000));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 6a303a9..34889ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.pm.PackageManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -31,13 +30,11 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -56,13 +53,11 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WMShellTest extends SysuiTestCase {
-    InputConsumerController mInputConsumerController;
     WMShell mWMShell;
 
     @Mock CommandQueue mCommandQueue;
     @Mock ConfigurationController mConfigurationController;
     @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock InputConsumerController mMockInputConsumerController;
     @Mock NavigationModeController mNavigationModeController;
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
@@ -70,6 +65,7 @@
     @Mock PipTouchHandler mPipTouchHandler;
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
+    @Mock HideDisplayCutout mHideDisplayCutout;
     @Mock ProtoTracer mProtoTracer;
     @Mock ShellDump mShellDump;
 
@@ -80,7 +76,8 @@
         mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
                 mKeyguardUpdateMonitor, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
-                Optional.of(mOneHanded), mProtoTracer, Optional.of(mShellDump));
+                Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
+                Optional.of(mShellDump));
 
         when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
     }
@@ -113,4 +110,12 @@
                 OneHandedGestureHandler.OneHandedGestureEventCallback.class));
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
     }
+
+    @Test
+    public void initHideDisplayCutout_registersCallbacks() {
+        mWMShell.initHideDisplayCutout(mHideDisplayCutout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index fb3c61b..e9edebd 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,6 +211,7 @@
         ":connectivity-service-srcs",
     ],
     installable: true,
+    jarjar_rules: "jarjar-rules.txt",
     libs: [
         "android.net.ipsec.ike",
         "services.core",
diff --git a/services/core/jarjar-rules.txt b/services/core/jarjar-rules.txt
new file mode 100644
index 0000000..58f7bcb
--- /dev/null
+++ b/services/core/jarjar-rules.txt
@@ -0,0 +1,2 @@
+# For ConnectivityService module
+rule com.android.modules.utils.** com.android.connectivity.utils.@1
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d586f00..1eba37d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -137,7 +137,6 @@
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.os.BasicShellCommandHandler;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -190,6 +189,7 @@
 import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BasicShellCommandHandler;
 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import com.android.server.am.BatteryStatsService;
diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java
index 7691852..d7e95df 100644
--- a/services/core/java/com/android/server/adb/AdbShellCommand.java
+++ b/services/core/java/com/android/server/adb/AdbShellCommand.java
@@ -16,7 +16,7 @@
 
 package com.android.server.adb;
 
-import android.os.BasicShellCommandHandler;
+import com.android.modules.utils.BasicShellCommandHandler;
 
 import java.io.PrintWriter;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d52cf02..eeec1bb 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -333,7 +333,7 @@
                 // initialization from here. AIDL HALs are initialized by FingerprintService since
                 // the HAL interface provides ID, strength, and other configuration information.
                 fingerprintService.initializeConfiguration(config.id, config.strength);
-                authenticator = new FingerprintAuthenticator(fingerprintService, config);
+                authenticator = new FingerprintAuthenticator(fingerprintService, config.id);
                 break;
 
             case TYPE_FACE:
@@ -348,7 +348,7 @@
                 // initialization from here. AIDL HALs are initialized by FaceService since
                 // the HAL interface provides ID, strength, and other configuration information.
                 faceService.initializeConfiguration(config.id, config.strength);
-                authenticator = new FaceAuthenticator(faceService, config);
+                authenticator = new FaceAuthenticator(faceService, config.id);
                 break;
 
             case TYPE_IRIS:
@@ -359,7 +359,8 @@
                     return;
                 }
 
-                authenticator = new IrisAuthenticator(irisService, config);
+                irisService.initializeConfiguration(config.id, config.strength);
+                authenticator = new IrisAuthenticator(irisService, config.id);
                 break;
 
             default:
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 3e0a40f..29424b4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -594,20 +594,6 @@
                 }
             }
 
-            // This happens infrequently enough, not worth caching.
-            final String[] configs = mInjector.getConfiguration(getContext());
-            boolean idFound = false;
-            for (int i = 0; i < configs.length; i++) {
-                SensorConfig config = new SensorConfig(configs[i]);
-                if (config.id == id) {
-                    idFound = true;
-                    break;
-                }
-            }
-            if (!idFound) {
-                throw new IllegalStateException("Cannot register unknown id");
-            }
-
             mSensors.add(new BiometricSensor(id, modality, strength, authenticator) {
                 @Override
                 boolean confirmationAlwaysRequired(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 88804e2..6741942 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -453,7 +453,7 @@
      * {@link SensorPropertiesInternal} strength.
      */
     public static @SensorProperties.Strength int authenticatorStrengthToPropertyStrength(
-            @BiometricManager.Authenticators.Types int strength) {
+            @Authenticators.Types int strength) {
         switch (strength) {
             case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
                 return SensorProperties.STRENGTH_CONVENIENCE;
@@ -465,4 +465,18 @@
                 throw new IllegalArgumentException("Unknown strength: " + strength);
         }
     }
+
+    public static @Authenticators.Types int propertyStrengthToAuthenticatorStrength(
+            @SensorProperties.Strength int strength) {
+        switch (strength) {
+            case SensorProperties.STRENGTH_CONVENIENCE:
+                return Authenticators.BIOMETRIC_CONVENIENCE;
+            case SensorProperties.STRENGTH_WEAK:
+                return Authenticators.BIOMETRIC_WEAK;
+            case SensorProperties.STRENGTH_STRONG:
+                return Authenticators.BIOMETRIC_STRONG;
+            default:
+                throw new IllegalArgumentException("Unknown strength: " + strength);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java
new file mode 100644
index 0000000..2ae6ccd
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+/**
+ * System_server services that require BiometricService to load before finishing initialization
+ * should implement this interface.
+ */
+public interface BiometricServiceCallback {
+    /**
+     * Notifies the service that BiometricService is initialized.
+     */
+    void onBiometricServiceReady();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index e742d10..b3e6cad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -32,10 +32,10 @@
     private final IFaceService mFaceService;
     private final int mSensorId;
 
-    public FaceAuthenticator(IFaceService faceService, SensorConfig config)
+    public FaceAuthenticator(IFaceService faceService, int sensorId)
             throws RemoteException {
         mFaceService = faceService;
-        mSensorId = config.id;
+        mSensorId = sensorId;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 65e57f1..1e0764a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,6 +27,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
@@ -54,7 +56,9 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
 import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.hidl.Face10;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -67,10 +71,11 @@
  * The service is responsible for maintaining a list of clients and dispatching all
  * face-related events.
  */
-public class FaceService extends SystemService {
+public class FaceService extends SystemService implements BiometricServiceCallback {
 
     protected static final String TAG = "FaceService";
 
+    private final FaceServiceWrapper mServiceWrapper;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull
@@ -506,21 +511,23 @@
                 @BiometricManager.Authenticators.Types int strength) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             mServiceProviders.add(
-                    new com.android.server.biometrics.sensors.face.hidl.Face10(getContext(),
-                            sensorId, strength, mLockoutResetDispatcher));
+                    new Face10(getContext(), sensorId, strength, mLockoutResetDispatcher));
         }
     }
 
     public FaceService(Context context) {
         super(context);
+        mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mServiceProviders = new ArrayList<>();
-
-        initializeAidlHals();
     }
 
-    private void initializeAidlHals() {
+    @Override
+    public void onBiometricServiceReady() {
+        final IBiometricService biometricService = IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+
         final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
         if (instances == null || instances.length == 0) {
             return;
@@ -543,6 +550,23 @@
                     final FaceProvider provider = new FaceProvider(getContext(), props, instance,
                             mLockoutResetDispatcher);
                     mServiceProviders.add(provider);
+
+                    // Register each sensor individually with BiometricService
+                    for (SensorProps prop : props) {
+                        final int sensorId = prop.commonProps.sensorId;
+                        @BiometricManager.Authenticators.Types int strength =
+                                Utils.propertyStrengthToAuthenticatorStrength(
+                                        prop.commonProps.sensorStrength);
+                        final FaceAuthenticator authenticator =
+                                new FaceAuthenticator(mServiceWrapper, sensorId);
+                        try {
+                            biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
+                                    authenticator);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Remote exception when registering sensorId: "
+                                    + sensorId);
+                        }
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
                 }
@@ -552,7 +576,7 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
+        publishBinderService(Context.FACE_SERVICE, mServiceWrapper);
     }
 
     private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index f77bc79..9f9d9cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -32,10 +32,10 @@
     private final IFingerprintService mFingerprintService;
     private final int mSensorId;
 
-    public FingerprintAuthenticator(IFingerprintService fingerprintService, SensorConfig config)
+    public FingerprintAuthenticator(IFingerprintService fingerprintService, int sensorId)
             throws RemoteException {
         mFingerprintService = fingerprintService;
-        mSensorId = config.id;
+        mSensorId = sensorId;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 99569b1..b84d095 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -24,14 +24,17 @@
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -65,6 +68,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
 import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
@@ -80,7 +84,7 @@
  * The service is responsible for maintaining a list of clients and dispatching all
  * fingerprint-related events.
  */
-public class FingerprintService extends SystemService {
+public class FingerprintService extends SystemService implements BiometricServiceCallback {
 
     protected static final String TAG = "FingerprintService";
 
@@ -88,6 +92,7 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     private final LockPatternUtils mLockPatternUtils;
+    private final FingerprintServiceWrapper mServiceWrapper;
     @NonNull private List<ServiceProvider> mServiceProviders;
 
     /**
@@ -572,7 +577,8 @@
         }
 
         @Override // Binder call
-        public void initializeConfiguration(int sensorId, int strength) {
+        public void initializeConfiguration(int sensorId,
+                @BiometricManager.Authenticators.Types int strength) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final Fingerprint21 fingerprint21;
@@ -626,16 +632,19 @@
 
     public FingerprintService(Context context) {
         super(context);
+        mServiceWrapper = new FingerprintServiceWrapper();
         mAppOps = context.getSystemService(AppOpsManager.class);
         mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mServiceProviders = new ArrayList<>();
-
-        initializeAidlHals();
     }
 
-    private void initializeAidlHals() {
+    @Override
+    public void onBiometricServiceReady() {
+        final IBiometricService biometricService = IBiometricService.Stub.asInterface(
+                        ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+
         final String[] instances = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
         if (instances == null || instances.length == 0) {
             return;
@@ -659,6 +668,23 @@
                             new FingerprintProvider(getContext(), props, instance,
                                     mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                     mServiceProviders.add(provider);
+
+                    // Register each sensor individually with BiometricService
+                    for (SensorProps prop : props) {
+                        final int sensorId = prop.commonProps.sensorId;
+                        @BiometricManager.Authenticators.Types int strength =
+                                Utils.propertyStrengthToAuthenticatorStrength(
+                                        prop.commonProps.sensorStrength);
+                        final FingerprintAuthenticator authenticator =
+                                new FingerprintAuthenticator(mServiceWrapper, sensorId);
+                        try {
+                            biometricService.registerAuthenticator(sensorId,
+                                    TYPE_FINGERPRINT, strength, authenticator);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Remote exception when registering sensorId: "
+                                    + sensorId);
+                        }
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
                 }
@@ -668,7 +694,7 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+        publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 0400ef5..b756d8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -31,10 +31,8 @@
 public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
     private final IIrisService mIrisService;
 
-    public IrisAuthenticator(IIrisService irisService, SensorConfig config) throws
-            RemoteException {
+    public IrisAuthenticator(IIrisService irisService, int sensorId) throws RemoteException {
         mIrisService = irisService;
-        mIrisService.initializeConfiguration(config.id);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index bcf63dc..08b2489 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -42,7 +42,7 @@
      */
     private final class IrisServiceWrapper extends IIrisService.Stub {
         @Override // Binder call
-        public void initializeConfiguration(int sensorId) {
+        public void initializeConfiguration(int sensorId, int strength) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
         }
     }
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index 909873f..3464704 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -16,9 +16,10 @@
 
 package com.android.server.location;
 
-import android.os.BasicShellCommandHandler;
 import android.os.UserHandle;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintWriter;
 import java.util.Objects;
 
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index bf5a50e..7a37bdd 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -820,7 +820,7 @@
                     CONTACTS_PERMISSIONS, STORAGE_PERMISSIONS);
         }
 
-        // Atthention Service
+        // Attention Service
         String attentionServicePackageName =
                 mContext.getPackageManager().getAttentionServicePackageName();
         if (!TextUtils.isEmpty(attentionServicePackageName)) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index be18d0a..76a5cda 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -22,8 +22,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
@@ -99,6 +102,12 @@
                             // layer
                             .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
                             .build())
+                    .addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
+                            FEATURE_HIDE_DISPLAY_CUTOUT)
+                            .all()
+                            .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
+                                    TYPE_NOTIFICATION_SHADE)
+                            .build())
                     .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                             FEATURE_ONE_HANDED)
                             .all()
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 9011ba0..61cc430 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2969,10 +2969,6 @@
 
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
-        final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
-                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
-        final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
-                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
         final boolean inSplitScreen =
                 mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
         if (inSplitScreen) {
@@ -2984,6 +2980,12 @@
         mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
+        final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
+                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+                mNonDockedStackBounds);
+        final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
+                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState,
+                mDockedStackBounds);
         final int disableFlags = win.getSystemUiVisibility() & StatusBarManager.DISABLE_MASK;
         final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
         final WindowState navColorWin = chooseNavigationColorWindowLw(
@@ -3049,10 +3051,14 @@
     }
 
     private int updateLightStatusBarLw(@Appearance int appearance, WindowState opaque,
-            WindowState opaqueOrDimming) {
+            WindowState opaqueOrDimming, Rect stackBounds) {
+        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+        final int statusBarHeight = mStatusBarHeightForRotation[displayRotation.getRotation()];
+        final boolean stackBoundsContainStatusBar =
+                stackBounds.isEmpty() ? false : stackBounds.top < statusBarHeight;
         final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
         final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
-        if (statusColorWin != null) {
+        if (stackBoundsContainStatusBar && statusColorWin != null) {
             if (statusColorWin == opaque || onKeyguard) {
                 // If the top fullscreen-or-dimming window is also the top fullscreen, respect
                 // its light flag.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index eff222a..ce61d50 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,8 +15,10 @@
  */
 package com.android.server.devicepolicy;
 
+import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
+import android.util.Slog;
 
 import com.android.server.SystemService;
 
@@ -30,6 +32,9 @@
  * should be added here to avoid build breakage in downstream branches.
  */
 abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
+
+    private static final String TAG = BaseIDevicePolicyManager.class.getSimpleName();
+
     /**
      * To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases.
      *
@@ -55,6 +60,16 @@
      */
     abstract void handleStopUser(int userId);
 
+    /**
+     * Sets the {@link DevicePolicySafetyChecker}.
+     *
+     * <p>Currently, it's called only by {@code SystemServer} on
+     * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}
+     */
+    public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+        Slog.w(TAG, "setDevicePolicySafetyChecker() not implemented by " + getClass());
+    }
+
     public void clearSystemUpdatePolicyFreezePeriodRecord() {
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a1167e9..f2c9718 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -135,9 +135,11 @@
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.DeviceStateCache;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.NetworkEvent;
@@ -148,6 +150,7 @@
 import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.UnsafeStateException;
 import android.app.backup.IBackupManager;
 import android.app.trust.TrustManager;
 import android.app.usage.UsageStatsManagerInternal;
@@ -634,6 +637,9 @@
     @VisibleForTesting
     final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager;
 
+    @Nullable
+    private DevicePolicySafetyChecker mSafetyChecker;
+
     public static final class Lifecycle extends SystemService {
         private BaseIDevicePolicyManager mService;
 
@@ -645,8 +651,8 @@
                 dpmsClassName = DevicePolicyManagerService.class.getName();
             }
             try {
-                Class serviceClass = Class.forName(dpmsClassName);
-                Constructor constructor = serviceClass.getConstructor(Context.class);
+                Class<?> serviceClass = Class.forName(dpmsClassName);
+                Constructor<?> constructor = serviceClass.getConstructor(Context.class);
                 mService = (BaseIDevicePolicyManager) constructor.newInstance(context);
             } catch (Exception e) {
                 throw new IllegalStateException(
@@ -655,6 +661,11 @@
             }
         }
 
+        /** Sets the {@link DevicePolicySafetyChecker}. */
+        public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+            mService.setDevicePolicySafetyChecker(safetyChecker);
+        }
+
         @Override
         public void onStart() {
             publishBinderService(Context.DEVICE_POLICY_SERVICE, mService);
@@ -953,6 +964,38 @@
         }
     }
 
+    @Override
+    public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+        Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker.getClass());
+        mSafetyChecker = safetyChecker;
+    }
+
+    /**
+     * Checks if the feature is supported and it's safe to execute the given {@code operation}.
+     *
+     * <p>Typically called at the beginning of each API method as:
+     *
+     * <pre><code>
+     *
+     * if (!canExecute(operation, permission)) return;
+     *
+     * </code></pre>
+     *
+     * @return {@code true} when it's safe to execute, {@code false} when the feature is not
+     * supported or the caller does not have the given {@code requiredPermission}.
+     *
+     * @throws UnsafeStateException if it's not safe to execute the operation.
+     */
+    boolean canExecute(@DevicePolicyOperation int operation, @NonNull String requiredPermission) {
+        if (!mHasFeature && !hasCallingPermission(requiredPermission)) {
+            return false;
+        }
+        if (mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation)) {
+            return true;
+        }
+        throw mSafetyChecker.newUnsafeStateException(operation);
+    }
+
     /**
      * Unit test will subclass it to inject mocks.
      */
@@ -1228,10 +1271,15 @@
             SystemProperties.set(key, value);
         }
 
+        // TODO (b/137101239): clean up split system user codes
         boolean userManagerIsSplitSystemUser() {
             return UserManager.isSplitSystemUser();
         }
 
+        boolean userManagerIsHeadlessSystemUserMode() {
+            return UserManager.isHeadlessSystemUserMode();
+        }
+
         String getDevicePolicyFilePathForSystemUser() {
             return "/data/system/";
         }
@@ -4755,9 +4803,10 @@
 
     @Override
     public void lockNow(int flags, boolean parent) {
-        if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
+        if (!canExecute(DevicePolicyManager.OPERATION_LOCK_NOW, permission.LOCK_DEVICE)) {
             return;
         }
+
         final CallerIdentity caller = getCallerIdentity();
 
         final int callingUserId = caller.getUserId();
@@ -7228,7 +7277,6 @@
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
 
-        // TODO In split system user mode, allow apps on user 0 to query the list
         synchronized (getLockObject()) {
             return getKeepUninstalledPackagesLocked();
         }
@@ -8550,6 +8598,8 @@
         pw.printf("mIsWatch=%b\n", mIsWatch);
         pw.printf("mIsAutomotive=%b\n", mIsAutomotive);
         pw.printf("mHasTelephonyFeature=%b\n", mHasTelephonyFeature);
+        String safetyChecker = mSafetyChecker == null ? "N/A" : mSafetyChecker.getClass().getName();
+        pw.printf("mSafetyChecker=%b\n", safetyChecker);
         pw.decreaseIndent();
     }
 
@@ -11988,6 +12038,9 @@
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(callingUserId);
+                // TODO (b/137101239): clean up split system user codes
+                //  ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
+                //  only supported on split-user systems.
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
                     return checkManagedUserProvisioningPreCondition(callingUserId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
@@ -12010,24 +12063,27 @@
         if (mOwners.hasProfileOwner(deviceOwnerUserId)) {
             return CODE_USER_HAS_PROFILE_OWNER;
         }
-        if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
+        // System user is always running in headless system user mode.
+        if (!mInjector.userManagerIsHeadlessSystemUserMode()
+                && !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
             return CODE_USER_NOT_RUNNING;
         }
         if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
             return CODE_HAS_PAIRED;
         }
+        // TODO (b/137101239): clean up split system user codes
         if (isAdb) {
-            // if shell command runs after user setup completed check device status. Otherwise, OK.
+            // If shell command runs after user setup completed check device status. Otherwise, OK.
             if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                if (!mInjector.userManagerIsSplitSystemUser()) {
-                    if (mUserManager.getUserCount() > 1) {
-                        return CODE_NONSYSTEM_USER_EXISTS;
-                    }
-                    if (hasIncompatibleAccountsOrNonAdb) {
-                        return CODE_ACCOUNTS_NOT_EMPTY;
-                    }
-                } else {
-                    // STOPSHIP Do proper check in split user mode
+                // In non-headless system user mode, DO can be setup only if
+                // there's no non-system user
+                if (!mInjector.userManagerIsHeadlessSystemUserMode()
+                        && !mInjector.userManagerIsSplitSystemUser()
+                        && mUserManager.getUserCount() > 1) {
+                    return CODE_NONSYSTEM_USER_EXISTS;
+                }
+                if (hasIncompatibleAccountsOrNonAdb) {
+                    return CODE_ACCOUNTS_NOT_EMPTY;
                 }
             }
             return CODE_OK;
@@ -12037,19 +12093,26 @@
                 if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
                     return CODE_NOT_SYSTEM_USER;
                 }
-                // In non-split user mode, only provision DO before setup wizard completes
+                // Only provision DO before setup wizard completes
+                // TODO (b/171423186): implement deferred DO setup for headless system user mode
                 if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
                     return CODE_USER_SETUP_COMPLETED;
                 }
-            } else {
+            }  else {
                 // STOPSHIP Do proper check in split user mode
             }
             return CODE_OK;
         }
     }
 
-    private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int deviceOwnerUserId) {
+    private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
         synchronized (getLockObject()) {
+            final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    ? UserHandle.USER_SYSTEM
+                    : callingUserId;
+            Slog.i(LOG_TAG,
+                    String.format("Calling user %d, device owner will be set on user %d",
+                            callingUserId, deviceOwnerUserId));
             // hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb.
             return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null,
                     deviceOwnerUserId, /* isAdb= */ false,
@@ -12057,6 +12120,7 @@
         }
     }
 
+    // TODO (b/137101239): clean up split system user codes
     private int checkManagedProfileProvisioningPreCondition(String packageName,
             @UserIdInt int callingUserId) {
         if (!hasFeatureManagedUsers()) {
@@ -12145,6 +12209,7 @@
         return null;
     }
 
+    // TODO (b/137101239): clean up split system user codes
     private int checkManagedUserProvisioningPreCondition(int callingUserId) {
         if (!hasFeatureManagedUsers()) {
             return CODE_MANAGED_USERS_NOT_SUPPORTED;
@@ -12166,6 +12231,7 @@
         return CODE_OK;
     }
 
+    // TODO (b/137101239): clean up split system user codes
     private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
         if (!mInjector.userManagerIsSplitSystemUser()) {
             // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
@@ -12738,9 +12804,8 @@
             return true;
         }
         if (userId == UserHandle.USER_SYSTEM) {
-            // The system user is always affiliated in a DO device, even if the DO is set on a
-            // different user. This could be the case if the DO is set in the primary user
-            // of a split user device.
+            // The system user is always affiliated in a DO device,
+            // even if in headless system user mode.
             return true;
         }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dfa726f..10b3265 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -34,6 +34,7 @@
 import android.app.ApplicationErrorReport;
 import android.app.INotificationManager;
 import android.app.SystemServiceRegistry;
+import android.app.admin.DevicePolicySafetyChecker;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -101,6 +102,7 @@
 import com.android.server.audio.AudioService;
 import com.android.server.biometrics.AuthService;
 import com.android.server.biometrics.BiometricService;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
 import com.android.server.biometrics.sensors.face.FaceService;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
 import com.android.server.biometrics.sensors.iris.IrisService;
@@ -195,8 +197,10 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Timer;
 import java.util.concurrent.CountDownLatch;
@@ -1468,7 +1472,10 @@
         }
         t.traceEnd();
 
-        if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+        final DevicePolicyManagerService.Lifecycle dpms;
+        if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+            dpms = null;
+        } else {
             t.traceBegin("StartLockSettingsService");
             try {
                 mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
@@ -1505,7 +1512,7 @@
             // Always start the Device Policy Manager, so that the API is compatible with
             // API8.
             t.traceBegin("StartDevicePolicyManager");
-            mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+            dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
             t.traceEnd();
 
             if (!isWatch) {
@@ -2089,9 +2096,12 @@
             final boolean hasFeatureFingerprint
                     = mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
 
+            final List<BiometricServiceCallback> biometricServiceCallback = new ArrayList<>();
             if (hasFeatureFace) {
                 t.traceBegin("StartFaceSensor");
-                mSystemServiceManager.startService(FaceService.class);
+                final FaceService faceService =
+                        mSystemServiceManager.startService(FaceService.class);
+                biometricServiceCallback.add(faceService);
                 t.traceEnd();
             }
 
@@ -2103,13 +2113,20 @@
 
             if (hasFeatureFingerprint) {
                 t.traceBegin("StartFingerprintSensor");
-                mSystemServiceManager.startService(FingerprintService.class);
+                final FingerprintService fingerprintService =
+                        mSystemServiceManager.startService(FingerprintService.class);
+                biometricServiceCallback.add(fingerprintService);
                 t.traceEnd();
             }
 
-            // Start this service after all biometric services.
+            // Start this service after all biometric sensor services are started.
             t.traceBegin("StartBiometricService");
             mSystemServiceManager.startService(BiometricService.class);
+            for (BiometricServiceCallback service : biometricServiceCallback) {
+                Slog.d(TAG, "Notifying onBiometricServiceReady for: "
+                        + service.getClass().getSimpleName());
+                service.onBiometricServiceReady();
+            }
             t.traceEnd();
 
             t.traceBegin("StartAuthService");
@@ -2427,6 +2444,9 @@
                 if (cshs instanceof Dumpable) {
                     mDumper.addDumpable((Dumpable) cshs);
                 }
+                if (cshs instanceof DevicePolicySafetyChecker) {
+                    dpms.setDevicePolicySafetyChecker((DevicePolicySafetyChecker) cshs);
+                }
                 t.traceEnd();
             }
 
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 1ccd512..a52fe12 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -22,13 +22,14 @@
 
 // Version of services.net for usage by the wifi mainline module.
 // Note: This is compiled against module_current.
-// TODO(b/145825329): This should be moved to networkstack-client,
+// TODO(b/172457099): This should be moved to networkstack-client,
 // with dependencies moved to frameworks/libs/net right.
 java_library {
     name: "services.net-module-wifi",
     srcs: [
         ":framework-services-net-module-wifi-shared-srcs",
         ":net-module-utils-srcs",
+        ":net-utils-services-common-srcs",
         "java/android/net/ip/IpClientCallbacks.java",
         "java/android/net/ip/IpClientManager.java",
         "java/android/net/ip/IpClientUtil.java",
@@ -39,6 +40,7 @@
         "java/android/net/TcpKeepalivePacketData.java",
     ],
     sdk_version: "module_current",
+    min_sdk_version: "30",
     libs: [
         "unsupportedappusage",
         "framework-wifi-util-lib",
@@ -49,7 +51,6 @@
         "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
-        "net-utils-services-common",
     ],
     apex_available: [
         "com.android.wifi",
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 1944965..0d878b4 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -28,7 +28,6 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.UpdateEngine;
 import android.os.UpdateEngineCallback;
 import android.provider.DeviceConfig;
@@ -227,8 +226,8 @@
         }
 
         // Sample for a fraction of app launches.
-        int traceFrequency =
-                SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2);
+        int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                "applaunch_trace_freq", 2);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         if (randomNum < traceFrequency) {
             try {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 3b60594..435c700 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1456,16 +1456,6 @@
                 mFingerprintAuthenticator);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testRegistrationWithUnknownId_throwsIllegalStateException() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
-        mBiometricService.onStart();
-
-        mBiometricService.mImpl.registerAuthenticator(
-                100 /* id */, 2 /* modality */, 15 /* strength */,
-                mFingerprintAuthenticator);
-    }
-
     @Test(expected = IllegalArgumentException.class)
     public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
             throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
index b0f7b0c..35fc7f0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
@@ -16,22 +16,28 @@
 
 package com.android.server.biometrics.sensors.face;
 
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.hardware.biometrics.BiometricManager;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.face.hidl.Face10;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+
 @Presubmit
 @SmallTest
 public class Face10Test {
@@ -41,6 +47,8 @@
 
     @Mock
     private Context mContext;
+    @Mock
+    private UserManager mUserManager;
 
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
@@ -54,10 +62,13 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-        mFace10 = new com.android.server.biometrics.sensors.face.hidl.Face10(mContext, SENSOR_ID,
-                BiometricManager.Authenticators.BIOMETRIC_STRONG, mLockoutResetDispatcher,
-                false /* supportsSelfIllumination */, 1 /* maxTemplatesAllowed */);
+        mFace10 = new Face10(mContext, SENSOR_ID, BiometricManager.Authenticators.BIOMETRIC_STRONG,
+                mLockoutResetDispatcher, false /* supportsSelfIllumination */,
+                1 /* maxTemplatesAllowed */);
         mBinder = new Binder();
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 64f8c58..632ad4c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -1140,6 +1140,25 @@
     }
 
     /**
+     * Stops and unloads all models. This is intended as a clean-up call with the expectation that
+     * this instance is not used after.
+     * @hide
+     */
+    public void detach() {
+        synchronized (mLock) {
+            for (ModelData model : mModelDataMap.values()) {
+                forceStopAndUnloadModelLocked(model, null);
+            }
+            mModelDataMap.clear();
+            internalClearGlobalStateLocked();
+            if (mModule != null) {
+                mModule.detach();
+                mModule = null;
+            }
+        }
+    }
+
+    /**
      * Stops and unloads a sound model, and removes any reference to the model if successful.
      *
      * @param modelData The model data to remove.
@@ -1170,7 +1189,7 @@
         }
         if (modelData.isModelStarted()) {
             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
-            if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
+            if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) {
                 modelData.setStopped();
                 modelData.setRequested(false);
             } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index f77d490..a976257 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -27,6 +27,7 @@
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.permission.Identity;
+import android.os.IBinder;
 
 import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 
@@ -46,10 +47,12 @@
     int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
     int STATUS_OK = SoundTrigger.STATUS_OK;
 
-    Session attachAsOriginator(@NonNull Identity originatorIdentity);
+    Session attachAsOriginator(@NonNull Identity originatorIdentity,
+            @NonNull IBinder client);
 
     Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
-            @NonNull Identity originatorIdentity);
+            @NonNull Identity originatorIdentity,
+            @NonNull IBinder client);
 
     /**
      * Dumps service-wide information.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 5999044..6c9f41c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -226,33 +226,45 @@
 
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
         @Override
-        public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity) {
+        public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity,
+                @NonNull IBinder client) {
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
-                return new SoundTriggerSessionStub(newSoundTriggerHelper());
+                return new SoundTriggerSessionStub(newSoundTriggerHelper(), client);
             }
         }
 
         @Override
         public ISoundTriggerSession attachAsMiddleman(Identity originatorIdentity,
-                Identity middlemanIdentity) {
+                Identity middlemanIdentity,
+                @NonNull IBinder client) {
             try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
                     SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity,
                     originatorIdentity)) {
-                return new SoundTriggerSessionStub(newSoundTriggerHelper());
+                return new SoundTriggerSessionStub(newSoundTriggerHelper(), client);
             }
         }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
         private final SoundTriggerHelper mSoundTriggerHelper;
+        // Used to detect client death.
+        private final IBinder mClient;
         private final TreeMap<UUID, SoundModel> mLoadedModels = new TreeMap<>();
         private final Object mCallbacksLock = new Object();
         private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
 
         SoundTriggerSessionStub(
-                SoundTriggerHelper soundTriggerHelper) {
+                SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) {
             mSoundTriggerHelper = soundTriggerHelper;
+            mClient = client;
+            try {
+                mClient.linkToDeath(() -> {
+                    clientDied();
+                }, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register death listener.", e);
+            }
         }
 
         @Override
@@ -790,6 +802,13 @@
             }
         }
 
+        private void clientDied() {
+            Slog.w(TAG, "Client died, cleaning up session.");
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "Client died, cleaning up session."));
+            mSoundTriggerHelper.detach();
+        }
+
         /**
          * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and
          * executed when the service connects.
@@ -1457,10 +1476,19 @@
 
         private class SessionImpl implements Session {
             private final @NonNull SoundTriggerHelper mSoundTriggerHelper;
+            private final @NonNull IBinder mClient;
 
             private SessionImpl(
-                    @NonNull SoundTriggerHelper soundTriggerHelper) {
+                    @NonNull SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) {
                 mSoundTriggerHelper = soundTriggerHelper;
+                mClient = client;
+                try {
+                    mClient.linkToDeath(() -> {
+                        clientDied();
+                    }, 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to register death listener.", e);
+                }
             }
 
             @Override
@@ -1507,22 +1535,31 @@
             public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
                 mSoundTriggerHelper.dump(fd, pw, args);
             }
+
+            private void clientDied() {
+                Slog.w(TAG, "Client died, cleaning up session.");
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "Client died, cleaning up session."));
+                mSoundTriggerHelper.detach();
+            }
         }
 
         @Override
-        public Session attachAsOriginator(@NonNull Identity originatorIdentity) {
+        public Session attachAsOriginator(@NonNull Identity originatorIdentity,
+                @NonNull IBinder client) {
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
-                return new SessionImpl(newSoundTriggerHelper());
+                return new SessionImpl(newSoundTriggerHelper(), client);
             }
         }
 
         @Override
         public Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
-                @NonNull Identity originatorIdentity) {
+                @NonNull Identity originatorIdentity,
+                @NonNull IBinder client) {
             try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
                     SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity, originatorIdentity)) {
-                return new SessionImpl(newSoundTriggerHelper());
+                return new SessionImpl(newSoundTriggerHelper(), client);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 547d253..2bcf3b5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -257,12 +257,13 @@
 
         @Override
         public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
-                @NonNull Identity originatorIdentity) {
+                @NonNull Identity originatorIdentity, IBinder client) {
             Objects.requireNonNull(originatorIdentity);
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
                 SoundTriggerSession session = new SoundTriggerSession(
-                        mSoundTriggerInternal.attachAsOriginator(IdentityContext.getNonNull()));
+                        mSoundTriggerInternal.attachAsOriginator(IdentityContext.getNonNull(),
+                                client));
                 synchronized (mSessions) {
                     mSessions.add(new WeakReference<>(session));
                 }
diff --git a/telephony/java/android/telephony/CarrierBandwidth.aidl b/telephony/java/android/telephony/CarrierBandwidth.aidl
new file mode 100644
index 0000000..d0861b8
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierBandwidth.aidl
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+parcelable CarrierBandwidth;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CarrierBandwidth.java b/telephony/java/android/telephony/CarrierBandwidth.java
new file mode 100644
index 0000000..17747a3
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierBandwidth.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Defines downlink and uplink capacity of a network in kbps
+ * @hide
+ */
+@SystemApi
+public final class CarrierBandwidth implements Parcelable {
+    /**
+     * Any field that is not reported shall be set to INVALID
+     */
+    public static final int INVALID = -1;
+
+    /**
+     * Estimated downlink capacity in kbps of the primary carrier.
+     * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+     * This will be {@link #INVALID} if the network is not connected
+     */
+    private int mPrimaryDownlinkCapacityKbps;
+
+    /**
+     * Estimated uplink capacity in kbps of the primary carrier.
+     * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+     * This will be {@link #INVALID} if the network is not connected
+     */
+    private int mPrimaryUplinkCapacityKbps;
+
+    /**
+     * Estimated downlink capacity in kbps of the secondary carrier in a dual connected network.
+     * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+     * This will be {@link #INVALID} if the network is not connected
+     */
+    private int mSecondaryDownlinkCapacityKbps;
+
+    /**
+     * Estimated uplink capacity in kbps of the secondary carrier in a dual connected network.
+     * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+     * This will be {@link #INVALID} if the network is not connected
+     */
+    private int mSecondaryUplinkCapacityKbps;
+
+    /** @hide **/
+    public CarrierBandwidth(Parcel in) {
+        mPrimaryDownlinkCapacityKbps = in.readInt();
+        mPrimaryUplinkCapacityKbps = in.readInt();
+        mSecondaryDownlinkCapacityKbps = in.readInt();
+        mSecondaryUplinkCapacityKbps = in.readInt();
+    }
+
+    /** @hide **/
+    public CarrierBandwidth() {
+        mPrimaryDownlinkCapacityKbps = INVALID;
+        mPrimaryUplinkCapacityKbps = INVALID;
+        mSecondaryDownlinkCapacityKbps = INVALID;
+        mSecondaryUplinkCapacityKbps = INVALID;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param primaryDownlinkCapacityKbps Estimated downlink capacity in kbps of
+     *        the primary carrier.
+     * @param primaryUplinkCapacityKbps Estimated uplink capacity in kbps of
+     *        the primary carrier.
+     * @param secondaryDownlinkCapacityKbps Estimated downlink capacity in kbps of
+     *        the secondary carrier
+     * @param secondaryUplinkCapacityKbps Estimated uplink capacity in kbps of
+     *        the secondary carrier
+     */
+    public CarrierBandwidth(int primaryDownlinkCapacityKbps, int primaryUplinkCapacityKbps,
+            int secondaryDownlinkCapacityKbps, int secondaryUplinkCapacityKbps) {
+        mPrimaryDownlinkCapacityKbps = primaryDownlinkCapacityKbps;
+        mPrimaryUplinkCapacityKbps = primaryUplinkCapacityKbps;
+        mSecondaryDownlinkCapacityKbps = secondaryDownlinkCapacityKbps;
+        mSecondaryUplinkCapacityKbps = secondaryUplinkCapacityKbps;
+    }
+
+    /**
+     * Retrieves the upstream bandwidth for the primary network in Kbps.  This always only refers to
+     * the estimated first hop transport bandwidth.
+     * This will be INVALID if the network is not connected
+     *
+     * @return The estimated first hop upstream (device to network) bandwidth.
+     */
+    public int getPrimaryDownlinkCapacityKbps() {
+        return mPrimaryDownlinkCapacityKbps;
+    }
+
+    /**
+     * Retrieves the downstream bandwidth for the primary network in Kbps.  This always only refers
+     * to the estimated first hop transport bandwidth.
+     * This will be INVALID if the network is not connected
+     *
+     * @return The estimated first hop downstream (network to device) bandwidth.
+     */
+    public int getPrimaryUplinkCapacityKbps() {
+        return mPrimaryUplinkCapacityKbps;
+    }
+
+    /**
+     * Retrieves the upstream bandwidth for the secondary network in Kbps.  This always only refers
+     * to the estimated first hop transport bandwidth.
+     * This will be INVALID if the network is not connected
+     *
+     * @return The estimated first hop upstream (device to network) bandwidth.
+     */
+    public int getSecondaryDownlinkCapacityKbps() {
+        return mSecondaryDownlinkCapacityKbps;
+    }
+
+    /**
+     * Retrieves the downstream bandwidth for the secondary network in Kbps.  This always only
+     * refers to the estimated first hop transport bandwidth.
+     * This will be INVALID if the network is not connected
+     *
+     * @return The estimated first hop downstream (network to device) bandwidth.
+     */
+    public int getSecondaryUplinkCapacityKbps() {
+        return mSecondaryUplinkCapacityKbps;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "CarrierBandwidth: {primaryDownlinkCapacityKbps=" + mPrimaryDownlinkCapacityKbps
+                + " primaryUplinkCapacityKbps=" + mPrimaryUplinkCapacityKbps
+                + " secondaryDownlinkCapacityKbps=" + mSecondaryDownlinkCapacityKbps
+                + " secondaryUplinkCapacityKbps=" + mSecondaryUplinkCapacityKbps
+                + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mPrimaryDownlinkCapacityKbps,
+                mPrimaryUplinkCapacityKbps,
+                mSecondaryDownlinkCapacityKbps,
+                mSecondaryUplinkCapacityKbps);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) {
+            return false;
+        }
+        if (this == o) {
+            return true;
+        }
+        CarrierBandwidth s = (CarrierBandwidth) o;
+        return (mPrimaryDownlinkCapacityKbps == s.mPrimaryDownlinkCapacityKbps
+                && mPrimaryUplinkCapacityKbps == s.mPrimaryUplinkCapacityKbps
+                && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps
+                && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPrimaryDownlinkCapacityKbps);
+        dest.writeInt(mPrimaryUplinkCapacityKbps);
+        dest.writeInt(mSecondaryDownlinkCapacityKbps);
+        dest.writeInt(mSecondaryUplinkCapacityKbps);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CarrierBandwidth> CREATOR =
+            new Parcelable.Creator() {
+            public CarrierBandwidth createFromParcel(Parcel in) {
+                return new CarrierBandwidth(in);
+            }
+
+            public CarrierBandwidth[] newArray(int size) {
+                return new CarrierBandwidth[size];
+            }
+    };
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index f8a200a..8507d85 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.AccessNetworkConstants.TransportType;
@@ -635,7 +636,8 @@
                 .append(" cellIdentity=").append(mCellIdentity)
                 .append(" voiceSpecificInfo=").append(mVoiceSpecificInfo)
                 .append(" dataSpecificInfo=").append(mDataSpecificInfo)
-                .append(" nrState=").append(nrStateToString(mNrState))
+                .append(" nrState=").append(Build.IS_DEBUGGABLE
+                        ? nrStateToString(mNrState) : "****")
                 .append(" rRplmn=").append(mRplmn)
                 .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
                 .append("}").toString();
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
index c2a4f33..b8c1ffe 100644
--- a/telephony/java/android/telephony/PinResult.java
+++ b/telephony/java/android/telephony/PinResult.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,10 +28,16 @@
 import java.util.Objects;
 
 /**
- * Holds the result from a pin attempt.
+ * Holds the result from a PIN attempt.
+ *
+ * @see TelephonyManager#supplyIccLockPin
+ * @see TelephonyManager#supplyIccLockPuk
+ * @see TelephonyManager#setIccLockEnabled
+ * @see TelephonyManager#changeIccLockPin
  *
  * @hide
  */
+@SystemApi
 public final class PinResult implements Parcelable {
     /** @hide */
     @IntDef({
@@ -64,24 +71,24 @@
     private static final PinResult sFailedResult =
             new PinResult(PinResult.PIN_RESULT_TYPE_FAILURE, -1);
 
-    private final @PinResultType int mType;
+    private final @PinResultType int mResult;
 
     private final int mAttemptsRemaining;
 
     /**
-     * Returns either success, incorrect or failure.
+     * Returns the result of the PIN attempt.
      *
-     * @see #PIN_RESULT_TYPE_SUCCESS
-     * @see #PIN_RESULT_TYPE_INCORRECT
-     * @see #PIN_RESULT_TYPE_FAILURE
-     * @return The result type of the pin attempt.
+     * @return The result of the PIN attempt.
      */
-    public @PinResultType int getType() {
-        return mType;
+    public @PinResultType int getResult() {
+        return mResult;
     }
 
     /**
-     * The number of pin attempts remaining.
+     * Returns the number of PIN attempts remaining.
+     * This will be set when {@link #getResult} is {@link #PIN_RESULT_TYPE_INCORRECT}.
+     * Indicates the number of attempts at entering the PIN before the SIM will be locked and
+     * require a PUK unlock to be performed.
      *
      * @return Number of attempts remaining.
      */
@@ -89,22 +96,32 @@
         return mAttemptsRemaining;
     }
 
+    /**
+     * Used to indicate a failed PIN attempt result.
+     *
+     * @return default PinResult for failure.
+     *
+     * @hide
+     */
     @NonNull
     public static PinResult getDefaultFailedResult() {
         return sFailedResult;
     }
 
     /**
-     * PinResult constructor
+     * PinResult constructor.
      *
-     * @param type The type of pin result.
+     * @param result The pin result value.
      * @see #PIN_RESULT_TYPE_SUCCESS
      * @see #PIN_RESULT_TYPE_INCORRECT
      * @see #PIN_RESULT_TYPE_FAILURE
+     * @see #PIN_RESULT_TYPE_ABORTED
      * @param attemptsRemaining Number of pin attempts remaining.
+     *
+     * @hide
      */
-    public PinResult(@PinResultType int type, int attemptsRemaining) {
-        mType = type;
+    public PinResult(@PinResultType int result, int attemptsRemaining) {
+        mResult = result;
         mAttemptsRemaining = attemptsRemaining;
     }
 
@@ -114,7 +131,7 @@
      * @hide
      */
     private PinResult(Parcel in) {
-        mType = in.readInt();
+        mResult = in.readInt();
         mAttemptsRemaining = in.readInt();
     }
 
@@ -124,11 +141,11 @@
     @NonNull
     @Override
     public String toString() {
-        return "type: " + getType() + ", attempts remaining: " + getAttemptsRemaining();
+        return "result: " + getResult() + ", attempts remaining: " + getAttemptsRemaining();
     }
 
     /**
-     * Required to be Parcelable
+     * Describe the contents of this object.
      */
     @Override
     public int describeContents() {
@@ -136,15 +153,17 @@
     }
 
     /**
-     * Required to be Parcelable
+     * Write this object to a Parcel.
      */
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeInt(mType);
+        out.writeInt(mResult);
         out.writeInt(mAttemptsRemaining);
     }
 
-    /** Required to be Parcelable */
+    /**
+     * Parcel creator class.
+     */
     public static final @NonNull Parcelable.Creator<PinResult> CREATOR = new Creator<PinResult>() {
         public PinResult createFromParcel(Parcel in) {
             return new PinResult(in);
@@ -156,7 +175,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mAttemptsRemaining, mType);
+        return Objects.hash(mAttemptsRemaining, mResult);
     }
 
     @Override
@@ -171,7 +190,7 @@
             return false;
         }
         PinResult other = (PinResult) obj;
-        return (mType == other.mType
+        return (mResult == other.mResult
                 && mAttemptsRemaining == other.mAttemptsRemaining);
     }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 41b3ee6..dedb1af 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1102,7 +1102,8 @@
                     .append(", isUsingCarrierAggregation=").append(isUsingCarrierAggregation())
                     .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
                     .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos)
-                    .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
+                    .append(", mNrFrequencyRange=").append(Build.IS_DEBUGGABLE
+                            ? mNrFrequencyRange : FREQUENCY_RANGE_UNKNOWN)
                     .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
                     .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
                     .append(", mIsDataRoamingFromRegistration=")
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e65b641..96f3ce0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8871,9 +8871,13 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyIccLockPin(String)} instead.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPinReportResult(String pin) {
         try {
             ITelephony telephony = getITelephony();
@@ -8885,65 +8889,91 @@
         return new int[0];
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyIccLockPuk(String, String)} instead.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPukReportResult(String puk, String pin) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
                 return telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#]", e);
+            Log.e(TAG, "Error calling ITelephony#supplyPukReportResultForSubscriber", e);
         }
         return new int[0];
     }
 
     /**
-     * Used when the user attempts to enter their pin.
+     * Supplies a PIN to unlock the ICC and returns the corresponding {@link PinResult}.
+     * Used when the user enters their ICC unlock PIN to attempt an unlock.
      *
-     * @param pin The user entered pin.
-     * @return The result of the pin.
+     * @param pin The user entered PIN.
+     * @return The result of the PIN.
+     * @throws SecurityException if the caller doesn't have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @hide
      */
-    @Nullable
+    @SystemApi
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public PinResult supplyPinReportPinResult(@NonNull String pin) {
+    public PinResult supplyIccLockPin(@NonNull String pin) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 int[] result = telephony.supplyPinReportResultForSubscriber(getSubId(), pin);
                 return new PinResult(result[0], result[1]);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#supplyPinReportResultForSubscriber", e);
+            Log.e(TAG, "Error calling ITelephony#supplyIccLockPin", e);
+            e.rethrowFromSystemServer();
         }
-        return null;
+        return PinResult.getDefaultFailedResult();
     }
 
     /**
-     * Used when the user attempts to enter the puk or their pin.
+     * Supplies a PUK and PIN to unlock the ICC and returns the corresponding {@link PinResult}.
+     * Used when the user enters their ICC unlock PUK and PIN to attempt an unlock.
      *
-     * @param puk The product unblocking key.
-     * @param pin The user entered pin.
-     * @return The result of the pin.
+     * @param puk The product unlocking key.
+     * @param pin The user entered PIN.
+     * @return The result of the PUK and PIN.
+     * @throws SecurityException if the caller doesn't have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @hide
      */
-    @Nullable
+    @SystemApi
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public PinResult supplyPukReportPinResult(@NonNull String puk, @NonNull String pin) {
+    public PinResult supplyIccLockPuk(@NonNull String puk, @NonNull String pin) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 int[] result = telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
                 return new PinResult(result[0], result[1]);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#]", e);
+            Log.e(TAG, "Error calling ITelephony#supplyIccLockPuk", e);
+            e.rethrowFromSystemServer();
         }
-        return null;
+        return PinResult.getDefaultFailedResult();
     }
 
     /**
@@ -13628,18 +13658,22 @@
     }
 
     /**
-     * The IccLock state or password was changed successfully.
+     * Indicates that the ICC PIN lock state or PIN was changed successfully.
      * @hide
      */
     public static final int CHANGE_ICC_LOCK_SUCCESS = Integer.MAX_VALUE;
 
     /**
-     * Check whether ICC pin lock is enabled.
-     * This is a sync call which returns the cached pin enabled state.
+     * Check whether ICC PIN lock is enabled.
+     * This is a sync call which returns the cached PIN enabled state.
      *
-     * @return {@code true} if ICC lock enabled, {@code false} if ICC lock disabled.
-     *
+     * @return {@code true} if ICC PIN lock enabled, {@code false} if disabled.
      * @throws SecurityException if the caller doesn't have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @hide
      */
@@ -13651,81 +13685,124 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 return telephony.isIccLockEnabled(getSubId());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
             Log.e(TAG, "isIccLockEnabled RemoteException", e);
+            e.rethrowFromSystemServer();
         }
         return false;
     }
 
     /**
-     * Set the ICC pin lock enabled or disabled.
+     * Enable or disable the ICC PIN lock.
      *
-     * If enable/disable ICC pin lock successfully, a value of {@link Integer#MAX_VALUE} is
-     * returned.
-     * If an incorrect old password is specified, the return value will indicate how many more
-     * attempts the user can make to change the password before the SIM is locked.
-     * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
-     *
-     * @param enabled    "true" for locked, "false" for unlocked.
-     * @param password   needed to change the ICC pin state, aka. Pin1
-     * @return an integer representing the status of IccLock enabled or disabled in the following
-     * three cases:
-     *   - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if enabled or disabled IccLock
-     *   successfully.
-     *   - Positive number and zero for remaining password attempts.
-     *   - Negative number for other failure cases (such like enabling/disabling PIN failed).
-     *
+     * @param enabled "true" for locked, "false" for unlocked.
+     * @param pin needed to change the ICC PIN lock, aka. Pin1.
+     * @return the result of enabling or disabling the ICC PIN lock.
      * @throws SecurityException if the caller doesn't have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @hide
      */
+    @SystemApi
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public int setIccLockEnabled(boolean enabled, @NonNull String password) {
-        checkNotNull(password, "setIccLockEnabled password can't be null.");
+    public PinResult setIccLockEnabled(boolean enabled, @NonNull String pin) {
+        checkNotNull(pin, "setIccLockEnabled pin can't be null.");
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.setIccLockEnabled(getSubId(), enabled, password);
+                int result = telephony.setIccLockEnabled(getSubId(), enabled, pin);
+                if (result == CHANGE_ICC_LOCK_SUCCESS) {
+                    return new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 0);
+                } else if (result < 0) {
+                    return PinResult.getDefaultFailedResult();
+                } else {
+                    return new PinResult(PinResult.PIN_RESULT_TYPE_INCORRECT, result);
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
             Log.e(TAG, "setIccLockEnabled RemoteException", e);
+            e.rethrowFromSystemServer();
         }
-        return 0;
+        return PinResult.getDefaultFailedResult();
     }
 
     /**
-     * Change the ICC password used in ICC pin lock.
+     * Change the ICC lock PIN.
      *
-     * If the password was changed successfully, a value of {@link Integer#MAX_VALUE} is returned.
-     * If an incorrect old password is specified, the return value will indicate how many more
-     * attempts the user can make to change the password before the SIM is locked.
-     * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
-     *
-     * @param oldPassword is the old password
-     * @param newPassword is the new password
-     * @return an integer representing the status of IccLock changed in the following three cases:
-     *   - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if changed IccLock successfully.
-     *   - Positive number and zero for remaining password attempts.
-     *   - Negative number for other failure cases (such like enabling/disabling PIN failed).
-     *
+     * @param oldPin is the old PIN
+     * @param newPin is the new PIN
+     * @return The result of changing the ICC lock PIN.
      * @throws SecurityException if the caller doesn't have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @hide
      */
+    @SystemApi
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public int changeIccLockPassword(@NonNull String oldPassword, @NonNull String newPassword) {
-        checkNotNull(oldPassword, "changeIccLockPassword oldPassword can't be null.");
-        checkNotNull(newPassword, "changeIccLockPassword newPassword can't be null.");
+    public PinResult changeIccLockPin(@NonNull String oldPin, @NonNull String newPin) {
+        checkNotNull(oldPin, "changeIccLockPin oldPin can't be null.");
+        checkNotNull(newPin, "changeIccLockPin newPin can't be null.");
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.changeIccLockPassword(getSubId(), oldPassword, newPassword);
+                int result = telephony.changeIccLockPassword(getSubId(), oldPin, newPin);
+                if (result == CHANGE_ICC_LOCK_SUCCESS) {
+                    return new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 0);
+                } else if (result < 0) {
+                    return PinResult.getDefaultFailedResult();
+                } else {
+                    return new PinResult(PinResult.PIN_RESULT_TYPE_INCORRECT, result);
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "changeIccLockPassword RemoteException", e);
+            Log.e(TAG, "changeIccLockPin RemoteException", e);
+            e.rethrowFromSystemServer();
         }
-        return 0;
+        return PinResult.getDefaultFailedResult();
+    }
+
+    /**
+     * Get carrier bandwidth. In case of Dual connected network this will report
+     * bandwidth per primary and secondary network.
+     * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @NonNull
+    public CarrierBandwidth getCarrierBandwidth() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getCarrierBandwidth(getSubId());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "getCarrierBandwidth RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+
+        //Should not reach. Adding return statement to make compiler happy
+        return null;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 51aa9cc..0bd4851 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -31,6 +31,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CallForwardingInfo;
+import android.telephony.CarrierBandwidth;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
@@ -2240,4 +2241,10 @@
      * @return true if dual connectivity is enabled else false
      */
     boolean isNrDualConnectivityEnabled(int subId);
+
+    /**
+     * Get carrier bandwidth per primary and secondary carrier
+     * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
+     */
+    CarrierBandwidth getCarrierBandwidth(int subId);
 }
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index d235c80..dc96df6 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -89,8 +89,6 @@
 rule android.util.LocalLog* com.android.wifi.x.@0
 rule android.util.Rational* com.android.wifi.x.@0
 
-rule android.os.BasicShellCommandHandler* com.android.wifi.x.@0
-
 # Use our statically linked bouncy castle library
 rule org.bouncycastle.** com.android.wifi.x.@0
 # Use our statically linked protobuf library