Merge "Bouncer - User switcher for large screens"
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f7e330..a65cf949 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31935,7 +31935,7 @@
     method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
     method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
-    method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
     method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
@@ -32503,6 +32503,7 @@
     field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final String DISALLOW_ADD_USER = "no_add_user";
+    field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
     field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -32558,6 +32559,7 @@
     field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
     field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+    field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
     field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
     field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
     field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
@@ -40453,6 +40455,7 @@
     field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
     field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY = "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
     field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
     field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_CROSS_SIM = 8192; // 0x2000
@@ -51344,7 +51347,7 @@
     method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
-    method public void addAudioDescriptionByDefaultStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionByDefaultStateChangeListener);
+    method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
     method @ColorInt public int getAccessibilityFocusColor();
@@ -51361,7 +51364,7 @@
     method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
     method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
-    method public boolean removeAudioDescriptionByDefaultStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionByDefaultStateChangeListener);
+    method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -51377,8 +51380,8 @@
     method public void onAccessibilityStateChanged(boolean);
   }
 
-  public static interface AccessibilityManager.AudioDescriptionByDefaultStateChangeListener {
-    method public void onAudioDescriptionByDefaultStateChanged(boolean);
+  public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+    method public void onAudioDescriptionRequestedChanged(boolean);
   }
 
   public static interface AccessibilityManager.TouchExplorationStateChangeListener {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dd951b4..c1d1518 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -224,6 +224,7 @@
     field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
     field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
+    field public static final String READ_COMMUNAL_STATE = "android.permission.READ_COMMUNAL_STATE";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
@@ -393,6 +394,7 @@
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemActivityRecognizer = 17039416; // 0x1040038
     field public static final int config_systemAmbientAudioIntelligence = 17039411; // 0x1040033
+    field public static final int config_systemAppProtectionService;
     field public static final int config_systemAudioIntelligence = 17039412; // 0x1040034
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
@@ -1309,6 +1311,20 @@
 
 }
 
+package android.app.communal {
+
+  public final class CommunalManager {
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void addCommunalModeListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.communal.CommunalManager.CommunalModeListener);
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public boolean isCommunalMode();
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void removeCommunalModeListener(@NonNull android.app.communal.CommunalManager.CommunalModeListener);
+  }
+
+  @java.lang.FunctionalInterface public static interface CommunalManager.CommunalModeListener {
+    method public void onCommunalModeChanged(boolean);
+  }
+
+}
+
 package android.app.compat {
 
   public final class CompatChanges {
@@ -2491,6 +2507,7 @@
     field public static final String BATTERY_STATS_SERVICE = "batterystats";
     field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
     field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
+    field public static final String COMMUNAL_SERVICE = "communal";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
     field public static final String ETHERNET_SERVICE = "ethernet";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index aa791aa..bf06db0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+    field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
@@ -608,6 +609,14 @@
 
 }
 
+package android.app.communal {
+
+  public final class CommunalManager {
+    method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean);
+  }
+
+}
+
 package android.app.contentsuggestions {
 
   public final class ContentSuggestionsManager {
@@ -819,6 +828,7 @@
     method public void holdLock(android.os.IBinder, int);
     method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
+    field public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index eb9ec86..b40fbee 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -497,7 +497,11 @@
         p.writeLong(notification.getPostedTimeMs());
         p.writeString(notification.getTitle());
         p.writeString(notification.getText());
-        notification.getIcon().writeToParcel(p, flags);
+        p.writeBoolean(false);
+        // The current design does not display icons, so don't bother adding them to the parcel
+        //if (notification.getIcon() != null) {
+        //    notification.getIcon().writeToParcel(p, flags);
+        //}
     }
 
     /**
@@ -539,7 +543,9 @@
         notificationOut.setPostedTimeMs(p.readLong());
         notificationOut.setTitle(p.readString());
         notificationOut.setText(p.readString());
-        notificationOut.setIcon(Icon.CREATOR.createFromParcel(p));
+        if (p.readBoolean()) {
+            notificationOut.setIcon(Icon.CREATOR.createFromParcel(p));
+        }
 
         return notificationOut.build();
     }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 089c269..81e6ae4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1513,7 +1513,7 @@
                     }
                 });
 
-        registerService(Context.COMMUNAL_MANAGER_SERVICE, CommunalManager.class,
+        registerService(Context.COMMUNAL_SERVICE, CommunalManager.class,
                 new CachedServiceFetcher<CommunalManager>() {
                     @Override
                     public CommunalManager createService(ContextImpl ctx) {
@@ -1522,7 +1522,7 @@
                             return null;
                         }
                         IBinder iBinder =
-                                ServiceManager.getService(Context.COMMUNAL_MANAGER_SERVICE);
+                                ServiceManager.getService(Context.COMMUNAL_SERVICE);
                         return iBinder != null ? new CommunalManager(
                                 ICommunalManager.Stub.asInterface(iBinder)) : null;
                     }
diff --git a/core/java/android/app/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java
index 60730ad..22f07693 100644
--- a/core/java/android/app/communal/CommunalManager.java
+++ b/core/java/android/app/communal/CommunalManager.java
@@ -17,15 +17,21 @@
 package android.app.communal;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.Overridable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import java.util.concurrent.Executor;
 
 /**
  * System private class for talking with the
@@ -33,10 +39,12 @@
  *
  * @hide
  */
-@SystemService(Context.COMMUNAL_MANAGER_SERVICE)
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+@SystemService(Context.COMMUNAL_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_COMMUNAL_MODE)
 public final class CommunalManager {
     private final ICommunalManager mService;
+    private final ArrayMap<CommunalModeListener, ICommunalModeListener> mCommunalModeListeners;
 
     /**
      * This change id is used to annotate packages which can run in communal mode by default,
@@ -59,15 +67,20 @@
     @Disabled
     public static final long ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT = 200324021L;
 
+    /** @hide */
     public CommunalManager(ICommunalManager service) {
         mService = service;
+        mCommunalModeListeners = new ArrayMap<CommunalModeListener, ICommunalModeListener>();
     }
 
     /**
      * Updates whether or not the communal view is currently showing over the lockscreen.
      *
      * @param isShowing Whether communal view is showing.
+     *
+     * @hide
      */
+    @TestApi
     @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE)
     public void setCommunalViewShowing(boolean isShowing) {
         try {
@@ -76,4 +89,72 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Checks whether or not the communal view is currently showing over the lockscreen.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public boolean isCommunalMode() {
+        try {
+            return mService.isCommunalMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Listener for communal state changes.
+     */
+    @FunctionalInterface
+    public interface CommunalModeListener {
+        /**
+         * Callback function that executes when the communal state changes.
+         */
+        void onCommunalModeChanged(boolean isCommunalMode);
+    }
+
+    /**
+     * Registers a callback to execute when the communal state changes.
+     *
+     * @param listener The listener to add to receive communal state changes.
+     * @param executor {@link Executor} to dispatch to. To dispatch the callback to the main
+     *                 thread of your application, use
+     *                 {@link android.content.Context#getMainExecutor()}.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public void addCommunalModeListener(@NonNull Executor executor,
+            @NonNull CommunalModeListener listener) {
+        synchronized (mCommunalModeListeners) {
+            try {
+                ICommunalModeListener iListener = new ICommunalModeListener.Stub() {
+                    @Override
+                    public void onCommunalModeChanged(boolean isCommunalMode) {
+                        executor.execute(() -> listener.onCommunalModeChanged(isCommunalMode));
+                    }
+                };
+                mService.addCommunalModeListener(iListener);
+                mCommunalModeListeners.put(listener, iListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a callback that executes when communal state changes.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public void removeCommunalModeListener(@NonNull CommunalModeListener listener) {
+        synchronized (mCommunalModeListeners) {
+            ICommunalModeListener iListener = mCommunalModeListeners.get(listener);
+            if (iListener != null) {
+                try {
+                    mService.removeCommunalModeListener(iListener);
+                    mCommunalModeListeners.remove(listener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/communal/ICommunalManager.aidl b/core/java/android/app/communal/ICommunalManager.aidl
index 02e8a65..869891e 100644
--- a/core/java/android/app/communal/ICommunalManager.aidl
+++ b/core/java/android/app/communal/ICommunalManager.aidl
@@ -16,12 +16,17 @@
 
 package android.app.communal;
 
+import android.app.communal.ICommunalModeListener;
+
 /**
  * System private API for talking with the communal manager service that handles communal mode
  * state.
  *
  * @hide
  */
-oneway interface ICommunalManager {
-    void setCommunalViewShowing(boolean isShowing);
+interface ICommunalManager {
+    oneway void setCommunalViewShowing(boolean isShowing);
+    boolean isCommunalMode();
+    void addCommunalModeListener(in ICommunalModeListener listener);
+    void removeCommunalModeListener(in ICommunalModeListener listener);
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/core/java/android/app/communal/ICommunalModeListener.aidl
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
rename to core/java/android/app/communal/ICommunalModeListener.aidl
index fbb78c8..006e782 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/core/java/android/app/communal/ICommunalModeListener.aidl
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.flags;
+package android.app.communal;
 
 /**
- * Class to manage simple DeviceConfig-based feature flags.
+ * System private API to be notified about communal mode changes.
  *
- * See {@link Flags} for instructions on defining new flags.
+ * @hide
  */
-public interface FeatureFlags extends FlagReader {
-}
+oneway interface ICommunalModeListener {
+    void onCommunalModeChanged(boolean isCommunalMode);
+}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c000e56..8ee38d3 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -23,7 +23,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Attributable;
 import android.content.AttributionSource;
@@ -170,7 +169,7 @@
                             mContext.getPackageManager(), 0);
                     intent.setComponent(comp);
                     if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                            UserHandle.CURRENT_OR_SELF)) {
+                            UserHandle.CURRENT)) {
                         Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
                         return false;
                     }
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index a254291..ecd5e40 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -103,7 +103,7 @@
                             mContext.getPackageManager(), 0);
                     intent.setComponent(comp);
                     if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                            UserHandle.CURRENT_OR_SELF)) {
+                            UserHandle.CURRENT)) {
                         logError("Could not bind to Bluetooth Service with " + intent);
                         return false;
                     }
diff --git a/core/java/android/bluetooth/le/TransportBlock.java b/core/java/android/bluetooth/le/TransportBlock.java
index b388bed..18bad9c 100644
--- a/core/java/android/bluetooth/le/TransportBlock.java
+++ b/core/java/android/bluetooth/le/TransportBlock.java
@@ -24,6 +24,7 @@
 
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 /**
  * Wrapper for Transport Discovery Data Transport Blocks.
@@ -59,8 +60,12 @@
         mOrgId = in.readInt();
         mTdsFlags = in.readInt();
         mTransportDataLength = in.readInt();
-        mTransportData = new byte[mTransportDataLength];
-        in.readByteArray(mTransportData);
+        if (mTransportDataLength > 0) {
+            mTransportData = new byte[mTransportDataLength];
+            in.readByteArray(mTransportData);
+        } else {
+            mTransportData = null;
+        }
     }
 
     @Override
@@ -68,7 +73,9 @@
         dest.writeInt(mOrgId);
         dest.writeInt(mTdsFlags);
         dest.writeInt(mTransportDataLength);
-        dest.writeByteArray(mTransportData);
+        if (mTransportData != null) {
+            dest.writeByteArray(mTransportData);
+        }
     }
 
     /**
@@ -79,6 +86,21 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        TransportBlock other = (TransportBlock) obj;
+        return Arrays.equals(toByteArray(), other.toByteArray());
+    }
+
     public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() {
         @Override
         public TransportBlock createFromParcel(Parcel in) {
diff --git a/core/java/android/bluetooth/le/TransportDiscoveryData.java b/core/java/android/bluetooth/le/TransportDiscoveryData.java
index c8e97f9..2b52f19 100644
--- a/core/java/android/bluetooth/le/TransportDiscoveryData.java
+++ b/core/java/android/bluetooth/le/TransportDiscoveryData.java
@@ -26,6 +26,7 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -96,6 +97,21 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        TransportDiscoveryData other = (TransportDiscoveryData) obj;
+        return Arrays.equals(toByteArray(), other.toByteArray());
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mTransportDataType);
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 1d4d30d..f335ae4 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -98,6 +99,7 @@
         boolean mAbortBroadcast;
         @UnsupportedAppUsage
         boolean mFinished;
+        String mReceiverClassName;
 
         /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -219,6 +221,12 @@
          * next broadcast will proceed.
          */
         public final void finish() {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "PendingResult#finish#ClassName:" + mReceiverClassName,
+                        1);
+            }
+
             if (mType == TYPE_COMPONENT) {
                 final IActivityManager mgr = ActivityManager.getService();
                 if (QueuedWork.hasPendingWork()) {
@@ -383,6 +391,14 @@
     public final PendingResult goAsync() {
         PendingResult res = mPendingResult;
         mPendingResult = null;
+
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            res.mReceiverClassName = getClass().getName();
+            Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "BroadcastReceiver#goAsync#ClassName:" + res.mReceiverClassName,
+                    1);
+        }
+
         return res;
     }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 73740d2c..543239b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5862,13 +5862,14 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.app.CommunalManager} for interacting with the global system state.
+     * {@link android.app.communal.CommunalManager} for interacting with the global system state.
      *
      * @see #getSystemService(String)
-     * @see android.app.CommunalManager
+     * @see android.app.communal.CommunalManager
      * @hide
      */
-    public static final String COMMUNAL_MANAGER_SERVICE = "communal_manager";
+    @SystemApi
+    public static final String COMMUNAL_SERVICE = "communal";
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 45d6ddd..ffe4ea4 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1374,18 +1374,18 @@
      * Returns if the activity should never be sandboxed to the activity window bounds.
      * @hide
      */
-    public boolean neverSandboxDisplayApis() {
+    public boolean neverSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) {
         return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS)
-                || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
+                || constrainDisplayApisConfig.getNeverConstrainDisplayApis(applicationInfo);
     }
 
     /**
      * Returns if the activity should always be sandboxed to the activity window bounds.
      * @hide
      */
-    public boolean alwaysSandboxDisplayApis() {
+    public boolean alwaysSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) {
         return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS)
-                || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo);
+                || constrainDisplayApisConfig.getAlwaysConstrainDisplayApis(applicationInfo);
     }
 
     /** @hide */
diff --git a/core/java/android/content/pm/ConstrainDisplayApisConfig.java b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
index 11ba3d4..98b73aa 100644
--- a/core/java/android/content/pm/ConstrainDisplayApisConfig.java
+++ b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
@@ -19,10 +19,15 @@
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
 
 import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.os.BackgroundThread;
+
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Class for processing flags in the Device Config namespace 'constrain_display_apis'.
@@ -55,19 +60,45 @@
             "always_constrain_display_apis";
 
     /**
+     * Indicates that display APIs should never be constrained to the activity window bounds for all
+     * packages.
+     */
+    private boolean mNeverConstrainDisplayApisAllPackages;
+
+    /**
+     * Indicates that display APIs should never be constrained to the activity window bounds for
+     * a set of defined packages. Map keys are package names, and entries are a
+     * 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private ArrayMap<String, Pair<Long, Long>> mNeverConstrainConfigMap;
+
+    /**
+     * Indicates that display APIs should always be constrained to the activity window bounds for
+     * a set of defined packages. Map keys are package names, and entries are a
+     * 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private ArrayMap<String, Pair<Long, Long>> mAlwaysConstrainConfigMap;
+
+    public ConstrainDisplayApisConfig() {
+        updateCache();
+
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                BackgroundThread.getExecutor(), properties -> updateCache());
+    }
+
+    /**
      * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the
      * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code
      * applicationInfo}.
      *
      * @param applicationInfo Information about the application/package.
      */
-    public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) {
-        if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
-                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) {
+    public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) {
+        if (mNeverConstrainDisplayApisAllPackages) {
             return true;
         }
 
-        return flagHasMatchingPackageEntry(FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+        return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo);
     }
 
     /**
@@ -76,73 +107,106 @@
      *
      * @param applicationInfo Information about the application/package.
      */
-    public static boolean alwaysConstrainDisplayApis(ApplicationInfo applicationInfo) {
-        return flagHasMatchingPackageEntry(FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+    public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) {
+        return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo);
+    }
+
+
+    /**
+     * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap},
+     * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}.
+     */
+    private void updateCache() {
+        mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false);
+
+        final String neverConstrainConfigStr = DeviceConfig.getString(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ "");
+        mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr);
+
+        final String alwaysConstrainConfigStr = DeviceConfig.getString(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ "");
+        mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr);
+    }
+
+    /**
+     * Processes the configuration string into a map of version codes, for the given
+     * configuration to be applied to the specified packages. If the given package
+     * entry string is invalid, then the map will not contain an entry for the package.
+     *
+     * @param configStr A configuration string expected to be in the format of a list of package
+     *                  entries separated by ','. A package entry expected to be in the format
+     *                  '<package-name>:<min-version-code>?:<max-version-code>?'.
+     * @return a map of configuration entries, where each key is a package name. Each value is
+     * a pair of version codes, in the format 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private static ArrayMap<String, Pair<Long, Long>> buildConfigMap(String configStr) {
+        ArrayMap<String, Pair<Long, Long>> configMap = new ArrayMap<>();
+        // String#split returns a non-empty array given an empty string.
+        if (configStr.isEmpty()) {
+            return configMap;
+        }
+        for (String packageEntryString : configStr.split(",")) {
+            List<String> packageAndVersions = Arrays.asList(packageEntryString.split(":", 3));
+            if (packageAndVersions.size() != 3) {
+                Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': "
+                        + packageEntryString);
+                // Skip this entry.
+                continue;
+            }
+            String packageName = packageAndVersions.get(0);
+            String minVersionCodeStr = packageAndVersions.get(1);
+            String maxVersionCodeStr = packageAndVersions.get(2);
+            try {
+                final long minVersion =
+                        minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong(
+                                minVersionCodeStr);
+                final long maxVersion =
+                        maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong(
+                                maxVersionCodeStr);
+                Pair<Long, Long> minMaxVersionCodes = new Pair<>(minVersion, maxVersion);
+                configMap.put(packageName, minMaxVersionCodes);
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString);
+                // Skip this entry.
+            }
+        }
+        return configMap;
     }
 
     /**
      * Returns true if the flag with the given {@code flagName} contains a package entry that
      * matches the given {@code applicationInfo}.
      *
+     * @param configMap the map representing the current configuration value to examine
      * @param applicationInfo Information about the application/package.
      */
-    private static boolean flagHasMatchingPackageEntry(String flagName,
+    private static boolean flagHasMatchingPackageEntry(Map<String, Pair<Long, Long>> configMap,
             ApplicationInfo applicationInfo) {
-        String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
-                flagName, /* defaultValue= */ "");
-
-        // String#split returns a non-empty array given an empty string.
-        if (configStr.isEmpty()) {
+        if (configMap.isEmpty()) {
             return false;
         }
-
-        for (String packageEntryString : configStr.split(",")) {
-            if (matchesApplicationInfo(packageEntryString, applicationInfo)) {
-                return true;
-            }
+        if (!configMap.containsKey(applicationInfo.packageName)) {
+            return false;
         }
-
-        return false;
+        return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo);
     }
 
     /**
-     * Parses the given {@code packageEntryString} and returns true if {@code
-     * applicationInfo.packageName} matches the package name in the config and {@code
-     * applicationInfo.longVersionCode} is within the version range in the config.
+     * Parses the given {@code minMaxVersionCodes} and returns true if {@code
+     * applicationInfo.longVersionCode} is within the version range in the pair.
+     * Returns false otherwise.
      *
-     * <p>Logs a warning and returns false in case the given {@code packageEntryString} is invalid.
-     *
-     * @param packageEntryStr A package entry expected to be in the format
-     *                        '<package-name>:<min-version-code>?:<max-version-code>?'.
+     * @param minMaxVersionCodes A pair expected to be in the format
+     *                        'Pair(<min-version-code>, <max-version-code>)'.
      * @param applicationInfo Information about the application/package.
      */
-    private static boolean matchesApplicationInfo(String packageEntryStr,
+    private static boolean matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes,
             ApplicationInfo applicationInfo) {
-        List<String> packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3));
-        if (packageAndVersions.size() != 3) {
-            Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': "
-                    + packageEntryStr);
-            return false;
-        }
-        String packageName = packageAndVersions.get(0);
-        String minVersionCodeStr = packageAndVersions.get(1);
-        String maxVersionCodeStr = packageAndVersions.get(2);
-
-        if (!packageName.equals(applicationInfo.packageName)) {
-            return false;
-        }
-        long version = applicationInfo.longVersionCode;
-        try {
-            if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) {
-                return false;
-            }
-            if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) {
-                return false;
-            }
-        } catch (NumberFormatException e) {
-            Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr);
-            return false;
-        }
-        return true;
+        return applicationInfo.longVersionCode >= minMaxVersionCodes.first
+                && applicationInfo.longVersionCode <= minMaxVersionCodes.second;
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3bf5f31..1c35b47 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2762,6 +2762,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports FeliCa communication, which is based on
+     * ISO/IEC 18092 and JIS X 6319-4.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_FELICA = "android.hardware.felica";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device's
      * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns
      * true.
@@ -3971,6 +3981,7 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.FEATURE)
+    @TestApi
     public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
 
     /** @hide */
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4c81f9c..0037464 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -30,6 +30,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.KeyguardManager;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -902,8 +903,16 @@
             @NonNull VirtualDisplayConfig virtualDisplayConfig,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
             @Nullable Context windowContext) {
-        return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
-                handler, windowContext);
+        return mGlobal.createVirtualDisplay(mContext, projection, null /* virtualDevice */,
+                virtualDisplayConfig, callback, handler, windowContext);
+    }
+
+    /** @hide */
+    public VirtualDisplay createVirtualDisplay(@Nullable IVirtualDevice virtualDevice,
+            @NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+        return mGlobal.createVirtualDisplay(mContext, null /* projection */, virtualDevice,
+                virtualDisplayConfig, callback, handler, null);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 75155bb..01833fd 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PropertyInvalidatedCache;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
@@ -582,14 +583,14 @@
     }
 
     public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,
-            @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,
-            Handler handler, @Nullable Context windowContext) {
+            IVirtualDevice virtualDevice, @NonNull VirtualDisplayConfig virtualDisplayConfig,
+            VirtualDisplay.Callback callback, Handler handler, @Nullable Context windowContext) {
         VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
         IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
         int displayId;
         try {
             displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper,
-                    projectionToken, context.getPackageName());
+                    projectionToken, virtualDevice, context.getPackageName());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 2985c75..83e1061 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.graphics.Point;
 import android.hardware.SensorManager;
-import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -381,31 +380,6 @@
     public abstract void onEarlyInteractivityChange(boolean interactive);
 
     /**
-     * A special API for creates a virtual display with a DisplayPolicyController in system_server.
-     * <p>
-     * If this method is called without original calling uid, the caller must enforce the
-     * corresponding permissions according to the flags.
-     *   {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
-     *   {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}
-     *   {@link android.Manifest.permission#ADD_TRUSTED_DISPLAY}
-     *   {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
-     * </p>
-     *
-     * @param virtualDisplayConfig The arguments for the virtual display configuration. See
-     *                             {@link VirtualDisplayConfig} for using it.
-     * @param callback Callback to call when the virtual display's state changes, or null if none.
-     * @param projection MediaProjection token.
-     * @param packageName The package name of the app.
-     * @param controller The DisplayWindowPolicyControl that can control what contents are
-     *                   allowed to be displayed.
-     * @return The newly created virtual display id , or {@link Display#INVALID_DISPLAY} if the
-     * virtual display cannot be created.
-     */
-    public abstract int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-            IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-            DisplayWindowPolicyController controller);
-
-    /**
      * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId}
      *
      * @param displayId The id of the display.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 0d5f1af..82b31d4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.display;
 
+import android.companion.virtual.IVirtualDevice;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Point;
 import android.hardware.display.BrightnessConfiguration;
@@ -86,10 +87,10 @@
     void requestColorMode(int displayId, int colorMode);
 
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
-    // MediaProjection token for certain combinations of flags.
+    // MediaProjection token or VirtualDevice for certain combinations of flags.
     int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
             in IVirtualDisplayCallback callback, in IMediaProjection projectionToken,
-            String packageName);
+            in IVirtualDevice virtualDevice, String packageName);
 
     // No permissions required, but must be same Uid as the creator.
     void resizeVirtualDisplay(in IVirtualDisplayCallback token,
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
index 50a6bfc..b3f7345 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -151,7 +151,7 @@
 
     /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
     @NonNull
-    public Set<String> getAllowedPlmnIds() {
+    public Set<String> getAllowedOperatorPlmnIds() {
         return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
     }
 
@@ -211,7 +211,7 @@
     }
 
     /** This class is used to incrementally build WifiNetworkPriority objects. */
-    public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+    public static final class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
@@ -233,7 +233,7 @@
          *     and {@link SubscriptionInfo#getMncString()}.
          */
         @NonNull
-        public Builder setAllowedPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
+        public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
             validatePlmnIds(allowedNetworkPlmnIds);
 
             mAllowedNetworkPlmnIds.clear();
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 31e38c0..55d3ecd 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -198,7 +198,10 @@
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
     @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
-    private static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+
     @NonNull private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities;
 
     private static final String MAX_MTU_KEY = "mMaxMtu";
@@ -229,6 +232,8 @@
         validate();
     }
 
+    // Null check MUST be done for all new fields added to VcnGatewayConnectionConfig, to avoid
+    // crashes when parsing PersistableBundle built on old platforms.
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
@@ -239,19 +244,30 @@
 
         final PersistableBundle exposedCapsBundle =
                 in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
-        final PersistableBundle networkPrioritiesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
-
         mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
         mTunnelConnectionParams =
                 TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
-        mUnderlyingNetworkPriorities =
-                new LinkedHashSet<>(
-                        PersistableBundleUtils.toList(
-                                networkPrioritiesBundle,
-                                VcnUnderlyingNetworkPriority::fromPersistableBundle));
+
+        final PersistableBundle networkPrioritiesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
+
+        if (networkPrioritiesBundle == null) {
+            // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus
+            // VcnGatewayConnectionConfig created on old platforms will not have this data and will
+            // be assigned with the default value
+            mUnderlyingNetworkPriorities =
+                    new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+
+        } else {
+            mUnderlyingNetworkPriorities =
+                    new LinkedHashSet<>(
+                            PersistableBundleUtils.toList(
+                                    networkPrioritiesBundle,
+                                    VcnUnderlyingNetworkPriority::fromPersistableBundle));
+        }
+
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
 
@@ -420,6 +436,7 @@
                 mGatewayConnectionName,
                 mTunnelConnectionParams,
                 mExposedCapabilities,
+                mUnderlyingNetworkPriorities,
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu);
     }
@@ -434,6 +451,7 @@
         return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
                 && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
+                && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu;
     }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
index 2ba9169..85eb100 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -79,7 +79,7 @@
         }
 
         final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other;
-        return mSsid == rhs.mSsid;
+        return mSsid.equals(rhs.mSsid);
     }
 
     /** @hide */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 53484d2..584f3c4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -634,7 +634,7 @@
      */
     public static int mapToInternalProcessState(int procState) {
         if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
-            return ActivityManager.PROCESS_STATE_NONEXISTENT;
+            return Uid.PROCESS_STATE_NONEXISTENT;
         } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
             return Uid.PROCESS_STATE_TOP;
         } else if (ActivityManager.isForegroundService(procState)) {
@@ -911,6 +911,11 @@
          * Total number of process states we track.
          */
         public static final int NUM_PROCESS_STATE = 7;
+        /**
+         * State of the UID when it has no running processes.  It is intentionally out of
+         * bounds 0..NUM_PROCESS_STATE.
+         */
+        public static final int PROCESS_STATE_NONEXISTENT = NUM_PROCESS_STATE;
 
         // Used in dump
         static final String[] PROCESS_STATE_NAMES = {
@@ -930,16 +935,6 @@
                 "C"   // CACHED
         };
 
-        /**
-         * When the process exits one of these states, we need to make sure cpu time in this state
-         * is not attributed to any non-critical process states.
-         */
-        public static final int[] CRITICAL_PROC_STATES = {
-                Uid.PROCESS_STATE_TOP,
-                Uid.PROCESS_STATE_FOREGROUND_SERVICE,
-                Uid.PROCESS_STATE_FOREGROUND
-        };
-
         public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
         public abstract Timer getProcessStateTimer(int state);
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index b3416e9..8292f26 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -67,6 +67,9 @@
     private static final String SYSTEM_DRIVER_NAME = "system";
     private static final String SYSTEM_DRIVER_VERSION_NAME = "";
     private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+    private static final String ANGLE_DRIVER_NAME = "angle";
+    private static final String ANGLE_DRIVER_VERSION_NAME = "";
+    private static final long ANGLE_DRIVER_VERSION_CODE = 0;
 
     // System properties related to updatable graphics drivers.
     private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
@@ -134,14 +137,24 @@
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
-        setupAngle(context, coreSettings, pm, packageName);
+        boolean useAngle = false;
+        if (setupAngle(context, coreSettings, pm, packageName)) {
+            if (shouldUseAngle(context, coreSettings, packageName)) {
+                useAngle = true;
+                setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+                        0, packageName, getVulkanVersion(pm));
+            }
+        }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
-            setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
-                    SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
-                    getVulkanVersion(pm));
+            if (!useAngle) {
+                setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
+                        SYSTEM_DRIVER_VERSION_CODE,
+                        SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
+                        packageName, getVulkanVersion(pm));
+            }
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 8894c85..4d5f97c 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4388,9 +4388,16 @@
      * The given class loader will be used to load any enclosed
      * Parcelables.
      * @return the Parcelable array, or null if the array is null
+     *
+     * @deprecated Use the type-safer version {@link #readParcelableArray(ClassLoader, Class)}
+     *      starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+     *      format to use {@link #createTypedArray(Parcelable.Creator)} if possible (eg. if the
+     *      items' class is final) since this is also more performant. Note that changing to the
+     *      latter also requires changing the writes.
      */
+    @Deprecated
     @Nullable
-    public final Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
+    public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
         int N = readInt();
         if (N < 0) {
             return null;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bc6dbd8..29d4d78 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -327,6 +327,43 @@
             "no_sharing_admin_configured_wifi";
 
     /**
+     * Specifies if a user is disallowed from using Wi-Fi Direct.
+     *
+     * <p>This restriction can only be set by a device owner,
+     * a profile owner of an organization-owned managed profile on the parent profile.
+     * When it is set by any of these owners, it prevents all users from using
+     * Wi-Fi Direct.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
+
+    /**
+     * Specifies if a user is disallowed from adding a new Wi-Fi configuration.
+     *
+     * <p>This restriction can only be set by a device owner,
+     * a profile owner of an organization-owned managed profile on the parent profile.
+     * When it is set by any of these owners, it prevents all users from adding
+     * a new Wi-Fi configuration. This does not limit the owner and carrier's ability
+     * to add a new configuration.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
+
+    /**
      * Specifies if a user is disallowed from changing the device
      * language. The default value is <code>false</code>.
      *
@@ -1500,6 +1537,8 @@
             DISALLOW_CHANGE_WIFI_STATE,
             DISALLOW_WIFI_TETHERING,
             DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            DISALLOW_WIFI_DIRECT,
+            DISALLOW_ADD_WIFI_CONFIG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 20f6c10..f0e6624 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -52,6 +52,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -150,7 +152,9 @@
     private ArrayMap<UserHandle, Context> mUserContexts;
     private PackageManager mPkgManager;
     private AppOpsManager mAppOpsManager;
-    private ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>();
+    @GuardedBy("mAttributionChains")
+    private final ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains =
+            new ArrayMap<>();
 
     /**
      * Constructor for PermissionUsageHelper
@@ -199,22 +203,24 @@
         // if any link in the chain is finished, remove the chain. Then, find any other chains that
         // contain this op/package/uid/tag combination, and remove them, as well.
         // TODO ntmyren: be smarter about this
-        mAttributionChains.remove(attributionChainId);
-        int numChains = mAttributionChains.size();
-        ArrayList<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < numChains; i++) {
-            int chainId = mAttributionChains.keyAt(i);
-            ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
-            int chainSize = chain.size();
-            for (int j = 0; j < chainSize; j++) {
-                AccessChainLink link = chain.get(j);
-                if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
-                    toRemove.add(chainId);
-                    break;
+        synchronized (mAttributionChains) {
+            mAttributionChains.remove(attributionChainId);
+            int numChains = mAttributionChains.size();
+            ArrayList<Integer> toRemove = new ArrayList<>();
+            for (int i = 0; i < numChains; i++) {
+                int chainId = mAttributionChains.keyAt(i);
+                ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
+                int chainSize = chain.size();
+                for (int j = 0; j < chainSize; j++) {
+                    AccessChainLink link = chain.get(j);
+                    if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
+                        toRemove.add(chainId);
+                        break;
+                    }
                 }
             }
+            mAttributionChains.removeAll(toRemove);
         }
-        mAttributionChains.removeAll(toRemove);
     }
 
     @Override
@@ -234,11 +240,13 @@
             // If this is not a successful start, or it is not a chain, or it is untrusted, return
             return;
         }
-        addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
-                attributionTag, attributionFlags, attributionChainId);
+        synchronized (mAttributionChains) {
+            addLinkToChainIfNotPresentLocked(AppOpsManager.opToPublicName(op), packageName, uid,
+                    attributionTag, attributionFlags, attributionChainId);
+        }
     }
 
-    private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
+    private void addLinkToChainIfNotPresentLocked(String op, String packageName, int uid,
             String attributionTag, int attributionFlags, int attributionChainId) {
 
         ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
@@ -544,42 +552,44 @@
             }
         }
 
-        for (int i = 0; i < mAttributionChains.size(); i++) {
-            List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
-            int lastVisible = usageList.size() - 1;
-            // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
-            // if the list is empty or incomplete, do not show it.
-            if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
-                    || !usageList.get(0).isStart()
-                    || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
-                continue;
-            }
-
-            //TODO ntmyren: remove once camera etc. etc.
-            for (AccessChainLink link: usageList) {
-                proxyPackages.add(link.usage.getPackageIdHash());
-            }
-
-            AccessChainLink start = usageList.get(0);
-            AccessChainLink lastVisibleLink = usageList.get(lastVisible);
-            while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
-                lastVisible--;
-                lastVisibleLink = usageList.get(lastVisible);
-            }
-            String proxyLabel = null;
-            if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
-                try {
-                    PackageManager userPkgManager =
-                            getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
-                    ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
-                            lastVisibleLink.usage.packageName, 0);
-                    proxyLabel = appInfo.loadLabel(userPkgManager).toString();
-                } catch (PackageManager.NameNotFoundException e) {
-                    // do nothing
+        synchronized (mAttributionChains) {
+            for (int i = 0; i < mAttributionChains.size(); i++) {
+                List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
+                int lastVisible = usageList.size() - 1;
+                // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
+                // if the list is empty or incomplete, do not show it.
+                if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
+                        || !usageList.get(0).isStart()
+                        || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
+                    continue;
                 }
 
+                //TODO ntmyren: remove once camera etc. etc.
+                for (AccessChainLink link : usageList) {
+                    proxyPackages.add(link.usage.getPackageIdHash());
+                }
+
+                AccessChainLink start = usageList.get(0);
+                AccessChainLink lastVisibleLink = usageList.get(lastVisible);
+                while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
+                    lastVisible--;
+                    lastVisibleLink = usageList.get(lastVisible);
+                }
+                String proxyLabel = null;
+                if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
+                    try {
+                        PackageManager userPkgManager =
+                                getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
+                        ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
+                                lastVisibleLink.usage.packageName, 0);
+                        proxyLabel = appInfo.loadLabel(userPkgManager).toString();
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // do nothing
+                    }
+
+                }
+                usagesAndLabels.put(start.usage, proxyLabel);
             }
-            usagesAndLabels.put(start.usage, proxyLabel);
         }
 
         for (int packageHash : mostRecentUsages.keySet()) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 49a211f..ad6d85f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9327,6 +9327,16 @@
                 "emergency_gesture_sound_enabled";
 
         /**
+         * The power button "cooldown" period in milliseconds after the Emergency gesture is
+         * triggered, during which single-key actions on the power button are suppressed. Cooldown
+         * period is disabled if set to zero.
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS =
+                "emergency_gesture_power_button_cooldown_period_ms";
+
+        /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
          *
@@ -16988,6 +16998,14 @@
                     "wear_activity_auto_resume_timeout_ms";
 
             /**
+             * If the current {@code WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS} value is set by user.
+             * 1 for true, 0 for false.
+             * @hide
+             */
+            public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER =
+                    "wear_activity_auto_resume_timeout_set_by_user";
+
+            /**
              * If burn in protection is enabled.
              * @hide
              */
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index ba0ac82..176a068 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -67,7 +67,8 @@
      *              include bold, italic, and normal. Values are constants defined
      *              in {@link Typeface}.
      * @param fontWeightAdjustment An integer describing the adjustment to be made to the font
-     *              weight.
+     *              weight. This is added to the value of the current weight returned by
+     *              {@link Typeface#getWeight()}.
      * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
      * that is used to reflect the current user's preference for increasing font weight.
      */
@@ -123,6 +124,9 @@
 
     /**
      * Returns the font weight adjustment specified by this span.
+     * <p>
+     * This can be {@link Configuration#FONT_WEIGHT_ADJUSTMENT_UNDEFINED}. This is added to the
+     * value of the current weight returned by {@link Typeface#getWeight()}.
      */
     public int getFontWeightAdjustment() {
         return mFontWeightAdjustment;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ba436e1..3c0597c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -948,9 +948,10 @@
     // When the listener is updated, we will get at least a single position update call so we can
     // guarantee any changes we post will be applied.
     private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
-            @Nullable Transaction geometryTransaction) {
+            Transaction geometryTransaction) {
         if (mPositionListener != null) {
             mRenderNode.removePositionUpdateListener(mPositionListener);
+            geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
         }
         mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
                 geometryTransaction);
@@ -958,7 +959,8 @@
     }
 
     private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
-            boolean creating, boolean sizeChanged, boolean hintChanged) {
+            boolean creating, boolean sizeChanged, boolean hintChanged,
+            Transaction geometryTransaction) {
         boolean realSizeChanged = false;
 
         mSurfaceLock.lock();
@@ -996,10 +998,6 @@
                 mSurfaceAlpha = alpha;
             }
 
-            // While creating the surface, we will set it's initial
-            // geometry. Outside of that though, we should generally
-            // leave it to the RenderThread.
-            Transaction geometryTransaction = new Transaction();
             geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
             if ((sizeChanged || hintChanged) && !creating) {
                 setBufferSize(geometryTransaction);
@@ -1022,20 +1020,18 @@
                             mSurfaceHeight);
                 }
 
-                boolean applyChangesOnRenderThread =
-                        sizeChanged && !creating && isHardwareAccelerated();
                 if (isHardwareAccelerated()) {
                     // This will consume the passed in transaction and the transaction will be
                     // applied on a render worker thread.
                     replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
-                            applyChangesOnRenderThread ? geometryTransaction : null);
+                            geometryTransaction);
                 }
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
-                            "%d updateSurfacePosition %s"
+                            "%d performSurfaceTransaction %s "
                                 + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
                             System.identityHashCode(this),
-                            applyChangesOnRenderThread ? "RenderWorker" : "UiThread",
+                            isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
                             mScreenRect.left, mScreenRect.top, mScreenRect.right,
                             mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
                 }
@@ -1147,12 +1143,14 @@
 
                 final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
+                // Collect all geometry changes and apply these changes on the RenderThread worker
+                // via the RenderNode.PositionUpdateListener.
+                final Transaction geometryTransaction = new Transaction();
                 if (creating) {
                     updateOpaqueFlag();
                     final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
                     if (mUseBlastAdapter) {
-                        createBlastSurfaceControls(viewRoot, name);
+                        createBlastSurfaceControls(viewRoot, name, geometryTransaction);
                     } else {
                         mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
                     }
@@ -1161,7 +1159,7 @@
                 }
 
                 final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
-                        translator, creating, sizeChanged, hintChanged);
+                        translator, creating, sizeChanged, hintChanged, geometryTransaction);
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished);
 
@@ -1335,7 +1333,8 @@
     // is still alive, the old buffers will continue to be presented until replaced by buffers from
     // the new adapter. This means we do not need to track the old surface control and destroy it
     // after the client has drawn to avoid any flickers.
-    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
+            Transaction geometryTransaction) {
         if (mSurfaceControl == null) {
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
@@ -1376,8 +1375,9 @@
         }
         mTransformHint = viewRoot.getBufferTransformHint();
         mBlastSurfaceControl.setTransformHint(mTransformHint);
-        mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                mSurfaceHeight, mFormat);
+        mBlastBufferQueue = new BLASTBufferQueue(name);
+        mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat,
+                geometryTransaction);
     }
 
     private void onDrawFinished(Transaction t) {
@@ -1558,6 +1558,10 @@
                 applyOrMergeTransaction(mRtTransaction, frameNumber);
             }
         }
+
+        public Transaction getTransaction() {
+            return mPositionChangedTransaction;
+        }
     }
 
     private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1651,6 +1655,11 @@
         @Override
         public void setFixedSize(int width, int height) {
             if (mRequestedWidth != width || mRequestedHeight != height) {
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format("%d setFixedSize %dx%d -> %dx%d",
+                            System.identityHashCode(this), mRequestedWidth, mRequestedHeight, width,
+                                    height));
+                }
                 mRequestedWidth = width;
                 mRequestedHeight = height;
                 requestLayout();
@@ -1660,6 +1669,10 @@
         @Override
         public void setSizeFromLayout() {
             if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format("%d setSizeFromLayout was %dx%d",
+                            System.identityHashCode(this), mRequestedWidth, mRequestedHeight));
+                }
                 mRequestedWidth = mRequestedHeight = -1;
                 requestLayout();
             }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c76245b..75d5ecf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -533,10 +533,11 @@
 
     boolean mReportNextDraw;
     /**
-     * Set if the reportDraw was requested from WM. If just a local report draw was invoked, there's
-     * no need to report back to system server and can just apply immediately on the client.
+     * Set whether the draw should use blast sync. This is in case the draw is canceled,
+     * but will be rescheduled. We still want the next draw to be sync.
      */
-    boolean mReportDrawToWm;
+    boolean mNextDrawUseBlastSync;
+
     boolean mFullRedrawNeeded;
     boolean mNewSurfaceNeeded;
     boolean mForceNextWindowRelayout;
@@ -2761,7 +2762,7 @@
             }
         }
         final boolean wasReportNextDraw = mReportNextDraw;
-        boolean useBlastSync = false;
+        boolean useBlastSync = mNextDrawUseBlastSync;
 
         if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
                 || mForceNextWindowRelayout) {
@@ -3292,9 +3293,11 @@
                 mPendingTransitions.clear();
             }
             performDraw(useBlastSync);
+            mNextDrawUseBlastSync = false;
         } else {
             if (isViewVisible) {
                 // Try again
+                mNextDrawUseBlastSync = useBlastSync;
                 scheduleTraversals();
             } else {
                 if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -3984,17 +3987,8 @@
         }
         mDrawsNeededToReport = 0;
 
-        if (!mReportDrawToWm) {
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "No need to report finishDrawing. Apply immediately");
-            }
-            mSurfaceChangedTransaction.apply();
-            return;
-        }
-
         try {
             mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
-            mReportDrawToWm = false;
         } catch (RemoteException e) {
             Log.e(mTag, "Unable to report draw finished", e);
             mSurfaceChangedTransaction.apply();
@@ -9604,7 +9598,6 @@
         if (mReportNextDraw == false) {
             drawPending();
         }
-        mReportDrawToWm = true;
         mReportNextDraw = true;
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 078b767..7a33507 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -273,8 +273,8 @@
     private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
             mServicesStateChangeListeners = new ArrayMap<>();
 
-    private final ArrayMap<AudioDescriptionByDefaultStateChangeListener, Executor>
-            mAudioDescriptionByDefaultStateChangeListeners = new ArrayMap<>();
+    private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
+            mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
 
     /**
      * Map from a view's accessibility id to the list of request preparers set for that view
@@ -359,15 +359,15 @@
      * Listener for the audio description by default state. To listen for
      * changes to the audio description by default state on the device,
      * implement this interface and register it with the system by calling
-     * {@link #addAudioDescriptionByDefaultStateChangeListener}.
+     * {@link #addAudioDescriptionRequestedChangeListener}.
      */
-    public interface AudioDescriptionByDefaultStateChangeListener {
+    public interface AudioDescriptionRequestedChangeListener {
         /**
          * Called when the audio description enabled state changes.
          *
          * @param enabled Whether audio description by default is enabled.
          */
-        void onAudioDescriptionByDefaultStateChanged(boolean enabled);
+        void onAudioDescriptionRequestedChanged(boolean enabled);
     }
 
     /**
@@ -1177,31 +1177,31 @@
     }
 
     /**
-     * Registers a {@link AudioDescriptionByDefaultStateChangeListener}
+     * Registers a {@link AudioDescriptionRequestedChangeListener}
      * for changes in the audio description by default state of the system.
      * The value could be read via {@link #isAudioDescriptionRequested}.
      *
      * @param executor The executor on which the listener should be called back.
      * @param listener The listener.
      */
-    public void addAudioDescriptionByDefaultStateChangeListener(
+    public void addAudioDescriptionRequestedChangeListener(
             @NonNull Executor executor,
-            @NonNull AudioDescriptionByDefaultStateChangeListener listener) {
+            @NonNull AudioDescriptionRequestedChangeListener listener) {
         synchronized (mLock) {
-            mAudioDescriptionByDefaultStateChangeListeners.put(listener, executor);
+            mAudioDescriptionRequestedChangeListeners.put(listener, executor);
         }
     }
 
     /**
-     * Unregisters a {@link AudioDescriptionByDefaultStateChangeListener}.
+     * Unregisters a {@link AudioDescriptionRequestedChangeListener}.
      *
      * @param listener The listener.
      * @return True if listener was previously registered.
      */
-    public boolean removeAudioDescriptionByDefaultStateChangeListener(
-            @NonNull AudioDescriptionByDefaultStateChangeListener listener) {
+    public boolean removeAudioDescriptionRequestedChangeListener(
+            @NonNull AudioDescriptionRequestedChangeListener listener) {
         synchronized (mLock) {
-            return (mAudioDescriptionByDefaultStateChangeListeners.remove(listener) != null);
+            return (mAudioDescriptionRequestedChangeListeners.remove(listener) != null);
         }
     }
 
@@ -1752,7 +1752,7 @@
      * </p>
      * <p>
      * Add listener to detect the state change via
-     * {@link #addAudioDescriptionByDefaultStateChangeListener}
+     * {@link #addAudioDescriptionRequestedChangeListener}
      * </p>
      * @return {@code true} if the audio description is enabled, {@code false} otherwise.
      */
@@ -1865,20 +1865,20 @@
      */
     private void notifyAudioDescriptionbyDefaultStateChanged() {
         final boolean isAudioDescriptionByDefaultRequested;
-        final ArrayMap<AudioDescriptionByDefaultStateChangeListener, Executor> listeners;
+        final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> listeners;
         synchronized (mLock) {
-            if (mAudioDescriptionByDefaultStateChangeListeners.isEmpty()) {
+            if (mAudioDescriptionRequestedChangeListeners.isEmpty()) {
                 return;
             }
             isAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
-            listeners = new ArrayMap<>(mAudioDescriptionByDefaultStateChangeListeners);
+            listeners = new ArrayMap<>(mAudioDescriptionRequestedChangeListeners);
         }
 
         final int numListeners = listeners.size();
         for (int i = 0; i < numListeners; i++) {
-            final AudioDescriptionByDefaultStateChangeListener listener = listeners.keyAt(i);
+            final AudioDescriptionRequestedChangeListener listener = listeners.keyAt(i);
             listeners.valueAt(i).execute(() ->
-                    listener.onAudioDescriptionByDefaultStateChanged(
+                    listener.onAudioDescriptionRequestedChanged(
                         isAudioDescriptionByDefaultRequested));
         }
     }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3d4d9ec..dfd853a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3696,18 +3696,21 @@
     }
 
     private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) {
+        if (hierarchyRoot == null) {
+            mBitmapCache = src.mBitmapCache;
+            mApplicationInfoCache = src.mApplicationInfoCache;
+        } else {
+            mBitmapCache = hierarchyRoot.mBitmapCache;
+            mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
+        }
         if (hierarchyRoot == null || src.mIsRoot) {
             // If there's no provided root, or if src was itself a root, then this RemoteViews is
             // the root of the new hierarchy.
             mIsRoot = true;
-            mBitmapCache = new BitmapCache();
-            mApplicationInfoCache = new ApplicationInfoCache();
             hierarchyRoot = this;
         } else {
             // Otherwise, we're a descendant in the hierarchy.
             mIsRoot = false;
-            mBitmapCache = hierarchyRoot.mBitmapCache;
-            mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
         }
         mApplication = src.mApplication;
         mLayoutId = src.mLayoutId;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 359c382..52122ee 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2736,7 +2736,7 @@
     }
 
     @Override
-    public void onListRebuilt(ResolverListAdapter listAdapter) {
+    public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
         setupScrollListener();
         maybeSetupGlobalLayoutListener();
 
@@ -2756,15 +2756,20 @@
             chooserListAdapter.updateAlphabeticalList();
         }
 
+        if (rebuildComplete) {
+            getChooserActivityLogger().logSharesheetAppLoadComplete();
+            maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
+        }
+    }
+
+    private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
         // don't support direct share on low ram devices
         if (ActivityManager.isLowRamDeviceStatic()) {
-            getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
 
         // no need to query direct share for work profile when its locked or disabled
         if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
-            getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
 
@@ -2775,8 +2780,6 @@
 
             queryDirectShareTargets(chooserListAdapter, false);
         }
-
-        getChooserActivityLogger().logSharesheetAppLoadComplete();
     }
 
     @VisibleForTesting
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fd8637a..b273f6d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1159,11 +1159,11 @@
         if (doPostProcessing) {
             maybeCreateHeader(listAdapter);
             resetButtonBar();
-            onListRebuilt(listAdapter);
+            onListRebuilt(listAdapter, rebuildCompleted);
         }
     }
 
-    protected void onListRebuilt(ResolverListAdapter listAdapter) {
+    protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
         final ItemClickListener listener = new ItemClickListener();
         setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
         if (shouldShowTabs() && isIntentPicker()) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aba43d8..c2224b4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
 import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
 import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
 
@@ -241,32 +242,16 @@
             MeasuredEnergyStats.POWER_BUCKET_CPU,
     };
 
+    // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
+    // Uid.PROCESS_STATE_NONEXISTENT, which is outside the range of legitimate proc states.
+    private static final int PROC_STATE_TIME_COUNTER_STATE_COUNT = NUM_PROCESS_STATE + 1;
+
     @GuardedBy("this")
     public boolean mPerProcStateCpuTimesAvailable = true;
 
-    /**
-     * When per process state cpu times tracking is off, cpu times in KernelSingleUidTimeReader are
-     * not updated. So, when the setting is turned on later, we would end up with huge cpu time
-     * deltas. This flag tracks the case where tracking is turned on from off so that we won't
-     * end up attributing the huge deltas to wrong buckets.
-     */
-    @GuardedBy("this")
-    private boolean mIsPerProcessStateCpuDataStale;
-
-    /**
-     * Uids for which per-procstate cpu times need to be updated.
-     *
-     * Contains uid -> procState mappings.
-     */
-    @GuardedBy("this")
-    @VisibleForTesting
-    protected final SparseIntArray mPendingUids = new SparseIntArray();
-
     @GuardedBy("this")
     private long mNumSingleUidCpuTimeReads;
     @GuardedBy("this")
-    private long mNumBatchedSingleUidCpuTimeReads;
-    @GuardedBy("this")
     private long mCpuTimeReadsTrackingStartTimeMs = SystemClock.uptimeMillis();
     @GuardedBy("this")
     private int mNumUidsRemoved;
@@ -443,88 +428,42 @@
     }
 
     /**
-     * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+     * Update per-freq cpu times for the supplied UID.
      */
-    public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
-        final SparseIntArray uidStates;
-        synchronized (BatteryStatsImpl.this) {
-            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
-                return;
-            }
-            if(!initKernelSingleUidTimeReaderLocked()) {
-                return;
-            }
-            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
-            // compute deltas since it might result in mis-attributing cpu times to wrong states.
-            if (mIsPerProcessStateCpuDataStale) {
-                mPendingUids.clear();
-                return;
-            }
-
-            if (mPendingUids.size() == 0) {
-                return;
-            }
-            uidStates = mPendingUids.clone();
-            mPendingUids.clear();
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
+    @VisibleForTesting
+    public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+        if (!initKernelSingleUidTimeReaderLocked()) {
+            return;
         }
-        final long timestampMs = mClock.elapsedRealtime();
-        LongArrayMultiStateCounter.LongArrayContainer deltaContainer = null;
-        for (int i = uidStates.size() - 1; i >= 0; --i) {
-            final int uid = uidStates.keyAt(i);
-            final int procState = uidStates.valueAt(i);
-            final int[] isolatedUids;
-            final LongArrayMultiStateCounter[] isolatedUidTimeInFreqCounters;
-            final Uid u;
-            synchronized (BatteryStatsImpl.this) {
-                // It's possible that uid no longer exists and any internal references have
-                // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
-                // creating an UidStats object if it doesn't already exist.
-                u = getAvailableUidStatsLocked(uid);
-                if (u == null) {
-                    continue;
-                }
-                if (u.mChildUids == null) {
-                    isolatedUids = null;
-                    isolatedUidTimeInFreqCounters = null;
-                } else {
-                    int childUidCount = u.mChildUids.size();
-                    isolatedUids = new int[childUidCount];
-                    isolatedUidTimeInFreqCounters = new LongArrayMultiStateCounter[childUidCount];
-                    for (int j = childUidCount - 1; j >= 0; --j) {
-                        isolatedUids[j] = u.mChildUids.keyAt(j);
-                        isolatedUidTimeInFreqCounters[j] =
-                                u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
-                        if (deltaContainer == null && isolatedUidTimeInFreqCounters[j] != null) {
-                            deltaContainer = getCpuTimeInFreqContainer();
-                        }
-                    }
+
+        final Uid u = getUidStatsLocked(uid);
+
+        mNumSingleUidCpuTimeReads++;
+
+        LongArrayMultiStateCounter onBatteryCounter =
+                u.getProcStateTimeCounter().getCounter();
+        LongArrayMultiStateCounter onBatteryScreenOffCounter =
+                u.getProcStateScreenOffTimeCounter().getCounter();
+
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+
+        if (u.mChildUids != null) {
+            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                    getCpuTimeInFreqContainer();
+            int childUidCount = u.mChildUids.size();
+            for (int j = childUidCount - 1; j >= 0; --j) {
+                LongArrayMultiStateCounter cpuTimeInFreqCounter =
+                        u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+                if (cpuTimeInFreqCounter != null) {
+                    mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
+                            cpuTimeInFreqCounter, timestampMs, deltaContainer);
+                    onBatteryCounter.addCounts(deltaContainer);
+                    onBatteryScreenOffCounter.addCounts(deltaContainer);
                 }
             }
-
-            LongArrayMultiStateCounter onBatteryCounter =
-                    u.getProcStateTimeCounter().getCounter();
-            LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                    u.getProcStateScreenOffTimeCounter().getCounter();
-
-            onBatteryCounter.setState(procState, timestampMs);
-            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-
-            onBatteryScreenOffCounter.setState(procState, timestampMs);
-            mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
-
-            if (isolatedUids != null) {
-                for (int j = isolatedUids.length - 1; j >= 0; --j) {
-                    if (isolatedUidTimeInFreqCounters[j] != null) {
-                        mKernelSingleUidTimeReader.addDelta(isolatedUids[j],
-                                isolatedUidTimeInFreqCounters[j], timestampMs, deltaContainer);
-                        onBatteryCounter.addCounts(deltaContainer);
-                        onBatteryScreenOffCounter.addCounts(deltaContainer);
-                    }
-                }
-            }
-
-            onBatteryCounter.setState(u.mProcessState, timestampMs);
-            onBatteryScreenOffCounter.setState(u.mProcessState, timestampMs);
         }
     }
 
@@ -542,24 +481,17 @@
         }
     }
 
-    public void copyFromAllUidsCpuTimes() {
-        synchronized (BatteryStatsImpl.this) {
-            copyFromAllUidsCpuTimes(
-                    mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning());
-        }
-    }
-
     /**
      * When the battery/screen state changes, we don't attribute the cpu times to any process
-     * but we still need to snapshots of all uids to get correct deltas later on. Since we
-     * already read this data for updating per-freq cpu times, we can use the same data for
-     * per-procstate cpu times.
+     * but we still need to take snapshots of all uids to get correct deltas later on.
      */
-    public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
+    @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
+    public void updateCpuTimesForAllUids() {
         synchronized (BatteryStatsImpl.this) {
-            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+            if (!trackPerProcStateCpuTimes()) {
                 return;
             }
+
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
@@ -567,14 +499,6 @@
             // TODO(b/197162116): just get a list of UIDs
             final SparseArray<long[]> allUidCpuFreqTimesMs =
                     mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
-            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
-            // compute deltas since it might result in mis-attributing cpu times to wrong states.
-            if (mIsPerProcessStateCpuDataStale) {
-                mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
-                mIsPerProcessStateCpuDataStale = false;
-                mPendingUids.clear();
-                return;
-            }
             for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
                 final int uid = allUidCpuFreqTimesMs.keyAt(i);
                 final int parentUid = mapUid(uid);
@@ -583,16 +507,8 @@
                     continue;
                 }
 
-                final int procState;
-                final int idx = mPendingUids.indexOfKey(uid);
-                if (idx >= 0) {
-                    procState = mPendingUids.valueAt(idx);
-                    mPendingUids.removeAt(idx);
-                } else {
-                    procState = u.mProcessState;
-                }
-
-                if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                final int procState = u.mProcessState;
+                if (procState == Uid.PROCESS_STATE_NONEXISTENT) {
                     continue;
                 }
 
@@ -602,27 +518,19 @@
                 final LongArrayMultiStateCounter onBatteryScreenOffCounter =
                         u.getProcStateScreenOffTimeCounter().getCounter();
 
-                onBatteryCounter.setState(procState, timestampMs);
                 if (uid == parentUid) {
                     mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-                }
-
-                onBatteryScreenOffCounter.setState(procState, timestampMs);
-                if (uid == parentUid) {
                     mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter,
                             timestampMs);
-                }
-
-                if (u.mChildUids != null) {
-                    for (int j = u.mChildUids.size() - 1; j >= 0; --j) {
-                        final LongArrayMultiStateCounter counter =
-                                u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+                } else {
+                    Uid.ChildUid childUid = u.getChildUid(uid);
+                    if (childUid != null) {
+                        final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
                         if (counter != null) {
-                            final int isolatedUid = u.mChildUids.keyAt(j);
                             final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
                                     getCpuTimeInFreqContainer();
-                            mKernelSingleUidTimeReader.addDelta(isolatedUid,
-                                    counter, timestampMs, deltaContainer);
+                            mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+                                    deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
                             onBatteryScreenOffCounter.addCounts(deltaContainer);
                         }
@@ -689,9 +597,6 @@
 
         Future<?> scheduleSync(String reason, int flags);
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
-        Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff,
-                long delayMillis);
-        Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
         Future<?> scheduleCpuSyncDueToSettingChange();
         /**
          * Schedule a sync because of a screen state change.
@@ -1839,10 +1744,6 @@
             return mCounter.getStateCount();
         }
 
-        public void setTrackingEnabled(boolean enabled, long timestampMs) {
-            mCounter.setEnabled(enabled && mTimeBase.isRunning(), timestampMs);
-        }
-
         private void setState(@BatteryConsumer.ProcessState int processState,
                 long elapsedRealtimeMs) {
             mCounter.setState(processState, elapsedRealtimeMs);
@@ -8073,7 +7974,7 @@
         Counter mBluetoothScanResultCounter;
         Counter mBluetoothScanResultBgCounter;
 
-        int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+        int mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
         StopwatchTimer[] mProcessStateTimer;
 
         boolean mInForegroundService = false;
@@ -8415,6 +8316,11 @@
             mChildUids.remove(idx);
         }
 
+        @GuardedBy("mBsi")
+        ChildUid getChildUid(int childUid) {
+            return mChildUids == null ? null : mChildUids.get(childUid);
+        }
+
         private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
             if (cpuTimesMs == null) {
                 return null;
@@ -8432,6 +8338,7 @@
             return null;
         }
 
+        @GuardedBy("mBsi")
         private void ensureMultiStateCounters() {
             if (mProcStateTimeMs != null) {
                 return;
@@ -8440,31 +8347,26 @@
             final long timestampMs = mBsi.mClock.elapsedRealtime();
             mProcStateTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
-                            NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            timestampMs);
             mProcStateScreenOffTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
-                            NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            timestampMs);
         }
 
+        @GuardedBy("mBsi")
         private TimeInFreqMultiStateCounter getProcStateTimeCounter() {
             ensureMultiStateCounters();
             return mProcStateTimeMs;
         }
 
+        @GuardedBy("mBsi")
         private TimeInFreqMultiStateCounter getProcStateScreenOffTimeCounter() {
             ensureMultiStateCounters();
             return mProcStateScreenOffTimeMs;
         }
 
-        private void setProcStateTimesTrackingEnabled(boolean enabled, long timestampMs) {
-            if (mProcStateTimeMs != null) {
-                mProcStateTimeMs.setTrackingEnabled(enabled, timestampMs);
-            }
-            if (mProcStateScreenOffTimeMs != null) {
-                mProcStateScreenOffTimeMs.setTrackingEnabled(enabled, timestampMs);
-            }
-        }
-
         @Override
         public Timer getAggregatedPartialWakelockTimer() {
             return mAggregatedPartialWakelockTimer;
@@ -8774,6 +8676,7 @@
                     processState);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getGnssMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
@@ -9553,7 +9456,7 @@
                 for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                     active |= !resetIfNotNull(mProcessStateTimer[i], false, realtimeUs);
                 }
-                active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
+                active |= (mProcessState != Uid.PROCESS_STATE_NONEXISTENT);
             }
             if (mVibratorOnTimer != null) {
                 if (mVibratorOnTimer.reset(false, realtimeUs)) {
@@ -10270,7 +10173,7 @@
             } else {
                 mBluetoothScanResultBgCounter = null;
             }
-            mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+            mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
             for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (in.readInt() != 0) {
                     makeProcessState(i, in);
@@ -10360,7 +10263,7 @@
                 // Read the object from the Parcel, whether it's usable or not
                 TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
                         mBsi.mOnBatteryTimeBase, in, timestampMs);
-                if (stateCount == NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     mProcStateTimeMs = counter;
                 }
             } else {
@@ -10373,7 +10276,7 @@
                 TimeInFreqMultiStateCounter counter =
                         new TimeInFreqMultiStateCounter(
                                 mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
-                if (stateCount == NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     mProcStateScreenOffTimeMs = counter;
                 }
             } else {
@@ -11244,12 +11147,6 @@
         }
 
         @GuardedBy("mBsi")
-        public void updateUidProcessStateLocked(int procState) {
-            updateUidProcessStateLocked(procState,
-                    mBsi.mClock.elapsedRealtime(), mBsi.mClock.uptimeMillis());
-        }
-
-        @GuardedBy("mBsi")
         public void updateUidProcessStateLocked(int procState,
                 long elapsedRealtimeMs, long uptimeMs) {
             int uidRunningState;
@@ -11263,40 +11160,35 @@
             }
 
             if (mProcessState != uidRunningState) {
-                if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                if (mProcessState != Uid.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
-
-                    if (mBsi.trackPerProcStateCpuTimes()) {
-                        if (mBsi.mPendingUids.size() == 0) {
-                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
-                                    mBsi.mOnBatteryTimeBase.isRunning(),
-                                    mBsi.mOnBatteryScreenOffTimeBase.isRunning(),
-                                    mBsi.mConstants.PROC_STATE_CPU_TIMES_READ_DELAY_MS);
-                            mBsi.mNumSingleUidCpuTimeReads++;
-                        } else {
-                            mBsi.mNumBatchedSingleUidCpuTimeReads++;
-                        }
-                        if (mBsi.mPendingUids.indexOfKey(mUid) < 0
-                                || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
-                            mBsi.mPendingUids.put(mUid, mProcessState);
-                        }
-                    } else {
-                        mBsi.mPendingUids.clear();
-                    }
                 }
-                mProcessState = uidRunningState;
-                if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                if (uidRunningState != Uid.PROCESS_STATE_NONEXISTENT) {
                     if (mProcessStateTimer[uidRunningState] == null) {
                         makeProcessState(uidRunningState, null);
                     }
                     mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
                 }
 
+                if (mBsi.trackPerProcStateCpuTimes()) {
+                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+
+                    LongArrayMultiStateCounter onBatteryCounter =
+                            getProcStateTimeCounter().getCounter();
+                    LongArrayMultiStateCounter onBatteryScreenOffCounter =
+                            getProcStateScreenOffTimeCounter().getCounter();
+
+                    onBatteryCounter.setState(uidRunningState, elapsedRealtimeMs);
+                    onBatteryScreenOffCounter.setState(uidRunningState, elapsedRealtimeMs);
+                }
+
+                mProcessState = uidRunningState;
+
                 updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
                 updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
 
                 final int batteryConsumerProcessState =
-                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+                        mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
                 getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
 
                 final MeasuredEnergyStats energyStats =
@@ -11318,7 +11210,7 @@
 
         /** Whether to consider Uid to be in the background for background timebase purposes. */
         public boolean isInBackground() {
-            // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+            // Note that PROCESS_STATE_CACHED and Uid.PROCESS_STATE_NONEXISTENT is
             // also considered to be 'background' for our purposes, because it's not foreground.
             return mProcessState >= PROCESS_STATE_BACKGROUND;
         }
@@ -15660,7 +15552,7 @@
 
     @GuardedBy("this")
     public boolean trackPerProcStateCpuTimes() {
-        return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+        return mCpuUidFreqTimeReader.isFastCpuTimesReader();
     }
 
     @GuardedBy("this")
@@ -15747,8 +15639,6 @@
 
     @VisibleForTesting
     public final class Constants extends ContentObserver {
-        public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
-                = "track_cpu_times_by_proc_state";
         public static final String KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME
                 = "track_cpu_active_cluster_time";
         public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
@@ -15766,9 +15656,7 @@
         public static final String KEY_BATTERY_CHARGED_DELAY_MS =
                 "battery_charged_delay_ms";
 
-        private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
         private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
-        private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
         private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
         private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
         private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
@@ -15779,9 +15667,7 @@
         private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
         private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
 
-        public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
         public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
-        public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
         /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
          * update when startObserving. */
         public long KERNEL_UID_READERS_THROTTLE_TIME;
@@ -15843,14 +15729,8 @@
                     Slog.e(TAG, "Bad batterystats settings", e);
                 }
 
-                updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
-                        mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
-                                DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
                 TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
                         KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
-                updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
-                        mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
-                                DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
                 updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
                         mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
                                 DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
@@ -15887,33 +15767,6 @@
                     DEFAULT_BATTERY_CHARGED_DELAY_MS);
         }
 
-        @GuardedBy("BatteryStatsImpl.this")
-        private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
-            TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
-            if (isEnabled && !wasEnabled) {
-                mIsPerProcessStateCpuDataStale = true;
-                mExternalSync.scheduleCpuSyncDueToSettingChange();
-
-                mNumSingleUidCpuTimeReads = 0;
-                mNumBatchedSingleUidCpuTimeReads = 0;
-                mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
-            }
-            final long timestampMs = mClock.elapsedRealtime();
-            for (int i = mUidStats.size() - 1; i >= 0; i--) {
-                mUidStats.valueAt(i).setProcStateTimesTrackingEnabled(isEnabled, timestampMs);
-            }
-        }
-
-        @GuardedBy("BatteryStatsImpl.this")
-        private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
-            PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
-            if (oldDelayMillis != newDelayMillis) {
-                mNumSingleUidCpuTimeReads = 0;
-                mNumBatchedSingleUidCpuTimeReads = 0;
-                mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
-            }
-        }
-
         private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
             KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
             if (oldTimeMs != newTimeMs) {
@@ -15932,12 +15785,8 @@
         }
 
         public void dumpLocked(PrintWriter pw) {
-            pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
-            pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
             pw.print(KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME); pw.print("=");
             pw.println(TRACK_CPU_ACTIVE_CLUSTER_TIME);
-            pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
-            pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
             pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
             pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
             pw.print(KEY_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS); pw.print("=");
@@ -16606,8 +16455,8 @@
             if (in.readInt() != 0) {
                 u.createBluetoothScanResultBgCounterLocked().readSummaryFromParcelLocked(in);
             }
-            u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+            u.mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (in.readInt() != 0) {
                     u.makeProcessState(i, null);
                     u.mProcessStateTimer[i].readSummaryFromParcelLocked(in);
@@ -16699,7 +16548,7 @@
                 // Read the object from the Parcel, whether it's usable or not
                 TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
                         mOnBatteryTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == Uid.NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     u.mProcStateTimeMs = counter;
                 }
             }
@@ -16714,7 +16563,7 @@
                 TimeInFreqMultiStateCounter counter =
                         new TimeInFreqMultiStateCounter(
                                 mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == Uid.NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     u.mProcStateScreenOffTimeMs = counter;
                 }
             }
@@ -17152,7 +17001,7 @@
             } else {
                 out.writeInt(0);
             }
-            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (u.mProcessStateTimer[i] != null) {
                     out.writeInt(1);
                     u.mProcessStateTimer[i].writeSummaryFromParcelLocked(out, nowRealtime);
@@ -17982,10 +17831,10 @@
         }
         super.dumpLocked(context, pw, flags, reqUid, histStart);
 
+        pw.print("Per process state tracking available: ");
+        pw.println(trackPerProcStateCpuTimes());
         pw.print("Total cpu time reads: ");
         pw.println(mNumSingleUidCpuTimeReads);
-        pw.print("Batched cpu time reads: ");
-        pw.println(mNumBatchedSingleUidCpuTimeReads);
         pw.print("Batching Duration (min): ");
         pw.println((mClock.uptimeMillis() - mCpuTimeReadsTrackingStartTimeMs) / (60 * 1000));
         pw.print("All UID cpu time reads since the later of device start or stats reset: ");
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index faeb8fc..c801be0 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -620,6 +620,10 @@
             }
             return numClusterFreqs;
         }
+
+        public boolean isFastCpuTimesReader() {
+            return mBpfTimesAvailable;
+        }
     }
 
     /**
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index a059dd6..78e5adc 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,21 +30,19 @@
 
 namespace android {
 
-static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
-                          jlong width, jlong height, jint format) {
-    String8 str8;
-    if (jName) {
-        const jchar* str16 = env->GetStringCritical(jName, nullptr);
-        if (str16) {
-            str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));
-            env->ReleaseStringCritical(jName, str16);
-            str16 = nullptr;
-        }
-    }
-    std::string name = str8.string();
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) {
+    ScopedUtfChars name(env, jName);
+    sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str());
+    queue->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(queue.get());
+}
+
+static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
+                                   jlong width, jlong height, jint format) {
+    ScopedUtfChars name(env, jName);
     sp<BLASTBufferQueue> queue =
-            new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
-                                 height, format);
+            new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl),
+                                 width, height, format);
     queue->incStrong((void*)nativeCreate);
     return reinterpret_cast<jlong>(queue.get());
 }
@@ -96,7 +94,8 @@
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
-        {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
+        {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate},
+        {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate},
         {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
         {"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction},
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 211f78e..23453876 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -380,6 +380,7 @@
     optional bool pip_auto_enter_enabled = 31;
     optional bool in_size_compat_mode = 32;
     optional float min_aspect_ratio = 33;
+    optional bool provides_max_bounds = 34;
 }
 
 /* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 04d6171..7018df2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2798,7 +2798,7 @@
     <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
-        android:protectionLevel="signature|role"
+        android:protectionLevel="signature|role|setup"
         android:label="@string/permlab_manageProfileAndDeviceOwners"
         android:description="@string/permdesc_manageProfileAndDeviceOwners" />
 
@@ -5531,10 +5531,18 @@
 
     <!-- Allows an application to interact with the currently active
         {@link com.android.server.communal.CommunalManagerService}.
-        @hide -->
+        @hide
+        @TestApi -->
     <permission android:name="android.permission.WRITE_COMMUNAL_STATE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to view information from the currently active
+         {@link com.android.server.communal.CommunalManagerService}.
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.READ_COMMUNAL_STATE"
+                android:protectionLevel="signature|privileged"/>
+
     <!-- Allows the holder to manage whether the system can bind to services
          provided by instant apps. This permission is intended to protect
          test/development fucntionality and should be used only in such cases.
@@ -5870,6 +5878,10 @@
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature|recents" />
+    <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+         window to the window where the touch currently is on top of.  @hide -->
+    <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+                android:protectionLevel="signature|recents" />
     <!--  Allows the caller to change the associations between input devices and displays.
         Very dangerous! @hide -->
     <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 606d0f2..92bea34 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -27,11 +27,11 @@
     <!-- The percentage of the screen width to use for the default width or height of
          picture-in-picture windows. Regardless of the percent set here, calculated size will never
          be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
-    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.14</item>
+    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.2</item>
 
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
-    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">56x27</string>
+    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">24x24</string>
 
     <!-- The default gravity for the picture-in-picture window.
          Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ccde348..480c679 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2123,6 +2123,8 @@
     <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string>
     <!-- The name of the package that will hold the device management role -->
     <string name="config_deviceManager" translatable="false"></string>
+    <!-- The name of the package that will hold the app protection service role. -->
+    <string name="config_systemAppProtectionService" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ca80def..1aa3ac2 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3267,6 +3267,8 @@
     <public name="config_systemSupervision" />
     <!-- @hide @SystemApi -->
     <public name="config_deviceManager" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemAppProtectionService" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
index 0456029..98485c0 100644
--- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -18,8 +18,7 @@
 
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.annotation.Nullable;
 import android.platform.test.annotations.Presubmit;
@@ -146,24 +145,17 @@
 
     private static void testNeverConstrainDisplayApis(String packageName, long version,
             boolean expected) {
-        boolean result = ConstrainDisplayApisConfig.neverConstrainDisplayApis(
-                buildApplicationInfo(packageName, version));
-        if (expected) {
-            assertTrue(result);
-        } else {
-            assertFalse(result);
-        }
+        ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig();
+        assertEquals(expected,
+                config.getNeverConstrainDisplayApis(buildApplicationInfo(packageName, version)));
     }
 
     private static void testAlwaysConstrainDisplayApis(String packageName, long version,
             boolean expected) {
-        boolean result = ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(
-                buildApplicationInfo(packageName, version));
-        if (expected) {
-            assertTrue(result);
-        } else {
-            assertFalse(result);
-        }
+        ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig();
+
+        assertEquals(expected,
+                config.getAlwaysConstrainDisplayApis(buildApplicationInfo(packageName, version)));
     }
 
     private static ApplicationInfo buildApplicationInfo(String packageName, long version) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 6be9306..bd4d80d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -169,13 +169,13 @@
 
         assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009–22/1/2009",
+        assertEquals("19/1/2009 – 22/1/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009–22/4/2009",
+        assertEquals("19/1/2009 – 22/4/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009–9/2/2012",
+        assertEquals("19/1/2009 – 9/2/2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -251,35 +251,35 @@
 
         assertEquals("19–22 de enero de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
-        assertEquals("19–22 de ene. de 2009",
+        assertEquals("19–22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene. – jue, 22 de ene. de 2009",
+        assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero–22 de abril de 2009",
+        assertEquals("19 de enero – 22 de abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19 de ene. – 22 de abr. 2009",
+        assertEquals("19 de ene – 22 de abr 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene. – mié, 22 de abr. de 2009",
+        assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("enero–abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19 de ene. de 2009 – 9 de feb. de 2012",
+        assertEquals("19 de ene de 2009 – 9 de feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("ene. de 2009 – feb. de 2012",
+        assertEquals("ene de 2009 – feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009–9 de febrero de 2012",
+        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_ES.
@@ -291,10 +291,10 @@
         assertEquals("lun, 19 ene – jue, 22 ene 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero–22 de abril de 2009",
+        assertEquals("19 de enero – 22 de abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
         assertEquals("19 ene – 22 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
@@ -311,9 +311,9 @@
         assertEquals("ene 2009 – feb 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009–9 de febrero de 2012",
+        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cfcbc7d..45504c0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -132,6 +132,8 @@
         });
     }
 
+    private static final String TEST_MIME_TYPE = "application/TestType";
+
     private static final int CONTENT_PREVIEW_IMAGE = 1;
     private static final int CONTENT_PREVIEW_FILE = 2;
     private static final int CONTENT_PREVIEW_TEXT = 3;
@@ -564,7 +566,7 @@
         assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK);
     }
 
-    @Test @Ignore
+    @Test
     public void copyTextToClipboardLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -585,17 +587,15 @@
         onView(withId(R.id.chooser_copy_button)).perform(click());
 
         verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
-        // First is  Activity shown, Second is "with preview"
-        assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
+
+        // The last captured event is the selection of the target.
+        assertThat(logMakerCaptor.getValue().getCategory(),
                 is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET));
-        assertThat(logMakerCaptor
-                        .getAllValues().get(2)
-                        .getSubtype(),
-                is(1));
+        assertThat(logMakerCaptor.getValue().getSubtype(), is(1));
     }
 
 
-    @Test @Ignore
+    @Test
     public void testNearbyShareLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -614,12 +614,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -628,22 +628,37 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_NEARBY_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_NEARBY_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
 
 
-    @Test @Ignore
+    @Test
     public void testEditImageLogs() throws Exception {
         Intent sendIntent = createSendImageIntent(
                 Uri.parse("android.resource://com.android.frameworks.coretests/"
@@ -668,11 +683,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("image/png"));
@@ -681,17 +697,32 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(1));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_EDIT_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_EDIT_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
 
@@ -778,7 +809,7 @@
     @Test
     public void testOnCreateLogging() {
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         MetricsLogger mockLogger = sOverrides.metricsLogger;
         ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
@@ -794,7 +825,7 @@
         assertThat(logMakerCaptor
                 .getAllValues().get(0)
                 .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
-                is("TestType"));
+                is(TEST_MIME_TYPE));
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getSubtype(),
@@ -804,7 +835,7 @@
     @Test
     public void testOnCreateLoggingFromWorkProfile() {
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.alternateProfileSetting = MetricsEvent.MANAGED_PROFILE;
         MetricsLogger mockLogger = sOverrides.metricsLogger;
         ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
@@ -820,7 +851,7 @@
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
-                is("TestType"));
+                is(TEST_MIME_TYPE));
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getSubtype(),
@@ -1476,7 +1507,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         markWorkProfileUserAvailable();
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1490,7 +1521,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1512,7 +1543,7 @@
                 workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         markWorkProfileUserAvailable();
 
         final ChooserWrapperActivity activity =
@@ -1538,7 +1569,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1561,7 +1592,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -1597,7 +1628,7 @@
         sOverrides.hasCrossProfileIntents = false;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1623,7 +1654,7 @@
         sOverrides.isQuietModeEnabled = true;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         final ChooserWrapperActivity activity =
@@ -1649,7 +1680,7 @@
                 createResolvedComponentsForTest(0);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1676,7 +1707,7 @@
         sOverrides.isQuietModeEnabled = true;
         sOverrides.hasCrossProfileIntents = false;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1701,7 +1732,7 @@
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         sOverrides.isQuietModeEnabled = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1714,7 +1745,7 @@
                 .check(matches(isDisplayed()));
     }
 
-    @Test @Ignore
+    @Test
     public void testAppTargetLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1743,12 +1774,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1757,17 +1788,32 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
     @Test @Ignore
@@ -1850,7 +1896,7 @@
                         .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
     }
 
-    @Test @Ignore
+    @Test
     public void testEmptyDirectRowLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         // We need app targets for direct targets to get displayed
@@ -1874,12 +1920,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1888,20 +1934,30 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_EMPTY_DIRECT_SHARE_ROW event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(5).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // SHARESHEET_EMPTY_DIRECT_SHARE_ROW:
+        assertThat(logger.event(4).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // Sixth is just an artifact of test set-up:
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        assertThat(logger.numCalls(), is(6));
     }
 
-    @Test @Ignore
+    @Test
     public void testCopyTextToClipboardLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1920,12 +1976,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1934,20 +1990,35 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
-    @Test @Ignore
+    @Test
     public void testSwitchProfileLogging() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1959,7 +2030,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1971,42 +2042,65 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(8));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
-        assertThat(logger.get(1).mimeType, is("TestType"));
+        assertThat(logger.get(1).mimeType, is(TEST_MIME_TYPE));
         assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth one is artifact of test setup
-        // fifth one is switch to work profile
-        assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(4).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
                 is(ChooserActivityLogger
-                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
-        // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(5).event.getId(),
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth is just an artifact of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // SHARESHEET_PROFILE_CHANGED:
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent
+                        .SHARESHEET_PROFILE_CHANGED.getId()));
+
+        // Repeat the loading steps in the new profile:
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(6).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // seventh one is artifact of test setup
-        // eigth one is switch to work profile
-        assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(7).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(7).getId(),
                 is(ChooserActivityLogger
-                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Ninth is again an artifact of test set-up:
+        assertThat(logger.event(8).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // SHARESHEET_PROFILE_CHANGED:
+        assertThat(logger.event(9).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent
+                        .SHARESHEET_PROFILE_CHANGED.getId()));
+
+        // No more events (this profile was already loaded).
+        assertThat(logger.numCalls(), is(10));
     }
 
     @Test
@@ -2019,7 +2113,7 @@
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -2043,7 +2137,7 @@
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -2244,7 +2338,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2275,7 +2369,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2299,7 +2393,7 @@
                 createResolvedComponentsForTest(3);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.isWorkProfileUserRunning = false;
 
         final ChooserWrapperActivity activity =
@@ -2331,7 +2425,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2355,7 +2449,7 @@
                 createResolvedComponentsForTest(3);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.isWorkProfileUserUnlocked = false;
 
         final ChooserWrapperActivity activity =
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 8ec33bf..c1a45c4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,7 +37,6 @@
 import android.app.ActivityManager;
 import android.os.BatteryStats;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.Display;
 
 import androidx.test.filters.LargeTest;
@@ -53,6 +52,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
 public class BatteryStatsImplTest {
     private static final long[] CPU_FREQS = {1, 2, 3, 4, 5};
     private static final int NUM_CPU_FREQS = CPU_FREQS.length;
@@ -62,29 +62,26 @@
     @Mock
     private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
+    private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStatsImpl;
 
-    private MockClock mMockClock = new MockClock();
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
         when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
         when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
         mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
                 .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
-                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
-                .setTrackingCpuByProcStateEnabled(true);
+                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
     }
 
     @Test
     public void testUpdateProcStateCpuTimes() {
         mBatteryStatsImpl.setOnBatteryInternal(true);
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] activityManagerProcStates = {
@@ -99,15 +96,24 @@
                 PROCESS_STATE_TOP,
                 PROCESS_STATE_CACHED
         };
-        addPendingUids(testUids, testProcStates);
 
         // Initialize time-in-freq counters
         mMockClock.realtime = 1000;
         for (int i = 0; i < testUids.length; ++i) {
-            mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
             mockKernelSingleUidTimeReader(testUids[i], new long[5]);
+            mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
         }
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
+
+        final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+
+        // Verify there are no cpu times initially.
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
+                assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
+            }
+        }
 
         // Obtain initial CPU time-in-freq counts
         final long[][] cpuTimes = {
@@ -117,24 +123,14 @@
                 {4859048, 348903, 4578967, 5973894, 298549}
         };
 
-        final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+        mMockClock.realtime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
-
-            // Verify there are no cpu times initially.
-            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
-            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
-                assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
-                assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
-            }
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -155,19 +151,19 @@
                 {945894, 9089432, 19478, 3834, 7845},
                 {843895, 43948, 949582, 99, 384}
         };
+
+        mMockClock.realtime += 1000;
+
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
             for (int j = 0; j < cpuTimes[i].length; j++) {
                 newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j];
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -186,10 +182,8 @@
         }
 
         // Validate the on-battery-screen-off counter
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
-                    mMockClock.realtime * 1000);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+                mMockClock.realtime * 1000);
 
         final long[][] delta2 = {
                 {95932, 2943, 49834, 89034, 139},
@@ -197,19 +191,19 @@
                 {678, 7498, 9843, 889, 4894},
                 {488, 998, 8498, 394, 574}
         };
+
+        mMockClock.realtime += 1000;
+
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
             for (int j = 0; j < cpuTimes[i].length; j++) {
                 newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j];
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -239,24 +233,25 @@
                 {3049509483598l, 4597834, 377654, 94589035, 7854},
                 {9493, 784, 99895, 8974893, 9879843}
         };
-        for (int i = 0; i < testUids.length; ++i) {
-            long[] newCpuTimes = new long[cpuTimes[i].length];
-            for (int j = 0; j < cpuTimes[i].length; j++) {
-                newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
-            }
-            mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
-        }
-        addPendingUids(testUids, testProcStates);
+
+        mMockClock.realtime += 1000;
+
         final int parentUid = testUids[1];
         final int childUid = 99099;
         addIsolatedUid(parentUid, childUid);
         final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
         mockKernelSingleUidTimeReader(childUid, isolatedUidCpuTimes, isolatedUidCpuTimes);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
+        for (int i = 0; i < testUids.length; ++i) {
+            long[] newCpuTimes = new long[cpuTimes[i].length];
+            for (int j = 0; j < cpuTimes[i].length; j++) {
+                newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
+            }
+            mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
+        }
 
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -284,11 +279,9 @@
     }
 
     @Test
-    public void testCopyFromAllUidsCpuTimes() {
+    public void testUpdateCpuTimesForAllUids() {
         mBatteryStatsImpl.setOnBatteryInternal(false);
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         mMockClock.realtime = 1000;
 
@@ -299,14 +292,14 @@
                 PROCESS_STATE_TOP,
                 PROCESS_STATE_CACHED
         };
-        addPendingUids(testUids, testProcStates);
 
         for (int i = 0; i < testUids.length; ++i) {
             BatteryStatsImpl.Uid uid = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
             uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
             mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.elapsedRealtime());
         }
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
 
         final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
         long[][] allCpuTimes = {
@@ -330,9 +323,8 @@
         }
 
         mMockClock.realtime += 1000;
-        mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false);
 
-        verifyNoPendingUids();
+        mBatteryStatsImpl.updateCpuTimesForAllUids();
 
         final long[] timeInFreqs = new long[NUM_CPU_FREQS];
 
@@ -411,9 +403,7 @@
         final int releaseTimeMs = 1005;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -436,9 +426,7 @@
         final int acquireTimeMs = 1000;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -464,9 +452,7 @@
         final int releaseTimeMs_2 = 1009;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -496,9 +482,7 @@
         final int releaseTimeMs_2 = 1009;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -523,17 +507,4 @@
         final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
         u.addIsolatedUid(childUid);
     }
-
-    private void addPendingUids(int[] uids, int[] procStates) {
-        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
-        for (int i = 0; i < uids.length; ++i) {
-            pendingUids.put(uids[i], procStates[i]);
-        }
-    }
-
-    private void verifyNoPendingUids() {
-        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
-        assertEquals("There shouldn't be any pending uids left: " + pendingUids,
-                0, pendingUids.size());
-    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 441e85d..9172d34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -26,9 +26,6 @@
 import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
-
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -51,7 +48,6 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DebugUtils;
 import android.util.KeyValueListParser;
@@ -125,11 +121,6 @@
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
         executeCmd("cmd deviceidle whitelist +" + TEST_PKG);
-
-        final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
-        desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
-        desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
-        updateBatteryStatsConstants(desiredConstants);
         checkCpuTimesAvailability();
     }
 
@@ -517,125 +508,6 @@
         batteryOffScreenOn();
     }
 
-    @Test
-    public void testCpuFreqTimes_trackingDisabled() throws Exception {
-        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
-            Log.w(TAG, "Skipping " + testName.getMethodName()
-                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
-                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
-            return;
-        }
-
-        final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS);
-        try {
-            batteryOnScreenOn();
-            forceStop();
-            resetBatteryStats();
-            final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-            assertNull("Initial snapshot should be null, initial="
-                    + Arrays.toString(initialSnapshot), initialSnapshot);
-            assertNull("Initial top state snapshot should be null",
-                    getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            final String msgCpuTimes = getAllCpuTimesMsg();
-            assertCpuTimesValid(cpuTimesMs);
-            long actualCpuTimeMs = 0;
-            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
-                actualCpuTimeMs += cpuTimesMs[i];
-            }
-            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
-                    WORK_DURATION_MS, actualCpuTimeMs);
-
-            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs2);
-            assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
-                    "Unexpected cpu times with tracking off");
-
-            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
-
-            final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs3);
-            assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 500,
-                    "Unexpected cpu times after turning on tracking");
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs4);
-            actualCpuTimeMs = 0;
-            for (int i = 0; i < cpuTimesMs4.length / 2; ++i) {
-                actualCpuTimeMs += cpuTimesMs4[i];
-            }
-            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
-                    2 * WORK_DURATION_MS, actualCpuTimeMs);
-
-            batteryOffScreenOn();
-        } finally {
-            Settings.Global.putString(sContext.getContentResolver(),
-                    Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
-        }
-    }
-
-    private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
-        for (int i = actual.length - 1; i >= 0; --i) {
-            if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
-                fail(errMsg + ", actual=" + Arrays.toString(actual)
-                        + ", expected=" + Arrays.toString(expected) + ", delta=" + delta);
-            }
-        }
-    }
-
-    private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
-            throws Exception {
-        final String newConstants;
-        final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
-        if (originalConstants == null || "null".equals(originalConstants)) {
-            newConstants = setting;
-        } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
-            newConstants = originalConstants.replaceAll(
-                    KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
-        } else {
-            newConstants = originalConstants + "," + setting;
-        }
-        Settings.Global.putString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
-        assertTrackPerProcStateCpuTimesSetting(enabled);
-    }
-
-    private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
-        final String expectedValue = Boolean.toString(enabled);
-        assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
-            final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
-            return expectedValue.equals(actualValue)
-                    ? null : "expected=" + expectedValue + ", actual=" + actualValue;
-        }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
-    }
-
-    private String getSettingValueFromDump(String key) throws Exception {
-        final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
-        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
-        splitter.setString(settingsDump);
-        String next;
-        while (splitter.hasNext()) {
-            next = splitter.next().trim();
-            if (next.startsWith(key)) {
-                return next.split("=")[1];
-            }
-        }
-        return null;
-    }
-
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1ae30db..d5b0f0a 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -98,6 +98,8 @@
                 new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
         supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
 
+        when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
+
         mStatsRule.getBatteryStats()
                 .setUserInfoProvider(mMockUserInfoProvider)
                 .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
@@ -277,8 +279,6 @@
 
     @Test
     public void testTimerBasedModel_byProcessState() {
-        mStatsRule.getBatteryStats().setTrackingCpuByProcStateEnabled(true);
-
         when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
 
         when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
@@ -311,7 +311,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         mockSingleUidTimeReader(APP_UID1, new long[]{1000, 2000, 3000, 4000});
         mockSingleUidTimeReader(APP_UID2, new long[]{1111, 2222, 3333, 4444});
@@ -326,7 +326,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         mockSingleUidTimeReader(APP_UID1, new long[] {5000, 6000, 7000, 8000});
         mockSingleUidTimeReader(APP_UID2, new long[]{5555, 6666, 7777, 8888});
@@ -346,7 +346,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         CpuPowerCalculator calculator =
                 new CpuPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index d16689c..e4c83f1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,10 +16,13 @@
 
 package com.android.internal.os;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.net.NetworkStats;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.SparseIntArray;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -59,6 +62,9 @@
         // A no-op handler.
         mHandler = new Handler(Looper.getMainLooper()) {
         };
+
+        mCpuUidFreqTimeReader = mock(KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader.class);
+        when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
     }
 
     public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -178,15 +184,6 @@
         return this;
     }
 
-    public MockBatteryStatsImpl setTrackingCpuByProcStateEnabled(boolean enabled) {
-        mConstants.TRACK_CPU_TIMES_BY_PROC_STATE = enabled;
-        return this;
-    }
-
-    public SparseIntArray getPendingUids() {
-        return mPendingUids;
-    }
-
     public int getAndClearExternalStatsSyncFlags() {
         final int flags = mExternalStatsSync.flags;
         mExternalStatsSync.flags = 0;
@@ -217,18 +214,6 @@
         }
 
         @Override
-        public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery,
-                boolean onBatteryScreenOff, long delayMillis) {
-            return null;
-        }
-
-        @Override
-        public Future<?> scheduleCopyFromAllUidsCpuTimes(
-                boolean onBattery, boolean onBatteryScreenOff) {
-            return null;
-        }
-
-        @Override
         public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
                 boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
             flags |= flag;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 60cb9d3..81db63e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -519,6 +519,9 @@
         <permission name="android.permission.LOCK_DEVICE" />
         <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+        <!-- Permission required for CTS test - CommunalManagerTest -->
+        <permission name="android.permission.WRITE_COMMUNAL_STATE" />
+        <permission name="android.permission.READ_COMMUNAL_STATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 9a41cb4..fa173072 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -189,7 +189,10 @@
         if (!actualPerm.containsAll(expectedPerm)) {
             return buildDescription(tree)
                     .setMessage("Method " + method.name.toString() + "() annotated " + expectedPerm
-                            + " but too wide; only invokes methods requiring " + actualPerm)
+                            + " but too wide; only invokes methods requiring " + actualPerm
+                            + "\n  If calling an AIDL interface, it can be annotated by adding:"
+                            + "\n  @JavaPassthrough(annotation=\""
+                            + "@android.annotation.RequiresPermission(...)\")")
                     .build();
         }
 
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 8844370..8d3eadb 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -27,8 +27,9 @@
     // Note: This field is accessed by native code.
     public long mNativeObject; // BLASTBufferQueue*
 
-    private static native long nativeCreate(String name, long surfaceControl, long width,
+    private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width,
             long height, int format);
+    private static native long nativeCreate(String name);
     private static native void nativeDestroy(long ptr);
     private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
     private static native void nativeSetSyncTransaction(long ptr, long transactionPtr,
@@ -43,7 +44,11 @@
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
             @PixelFormat.Format int format) {
-        mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
+        mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format);
+    }
+
+    public BLASTBufferQueue(String name) {
+        mNativeObject = nativeCreate(name);
     }
 
     public void destroy() {
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
new file mode 100644
index 0000000..2758704
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:color="@color/tv_pip_menu_icon_focused" />
+    <item android:state_enabled="false"
+          android:color="@color/tv_pip_menu_icon_disabled" />
+    <item android:color="@color/tv_pip_menu_icon_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
new file mode 100644
index 0000000..4f5e63d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:color="@color/tv_pip_menu_icon_bg_focused" />
+    <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
similarity index 74%
rename from libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml
rename to libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
index cce1303..1938f45 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
@@ -14,5 +14,8 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#9AFFFFFF" android:radius="17dp" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_button_radius" />
+    <solid android:color="@color/tv_pip_menu_icon_bg" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
similarity index 60%
rename from packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
rename to libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 59a31e8..9bc0311 100644
--- a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -12,19 +12,11 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
+  ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="?androidprv:attr/colorAccentPrimary"
-        android:width="1dp"/>
-    <corners android:radius="24dp"/>
-    <padding
-        android:left="16dp"
-        android:right="16dp"
-        android:top="8dp"
-        android:bottom="8dp" />
-    <solid android:color="@android:color/transparent" />
-</shape>
+    android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_border_radius" />
+    <stroke android:width="@dimen/pip_menu_border_width"
+            android:color="@color/tv_pip_menu_focus_border" />
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 49e2379..5b90c99 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -18,35 +18,54 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/tv_pip_menu"
              android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:background="#CC000000">
+             android:layout_height="match_parent">
 
-    <LinearLayout
-        android:id="@+id/tv_pip_menu_action_buttons"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginTop="350dp"
-        android:orientation="horizontal"
-        android:alpha="0">
+    <FrameLayout
+        android:id="@+id/tv_pip_menu_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:alpha="0" >
 
-        <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-            android:id="@+id/tv_pip_menu_fullscreen_button"
-            android:layout_width="@dimen/picture_in_picture_button_width"
-            android:layout_height="wrap_content"
-            android:src="@drawable/pip_ic_fullscreen_white"
-            android:text="@string/pip_fullscreen" />
+        <HorizontalScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_centerHorizontal="true"
+            android:gravity="center"
+            android:scrollbars="none"
+            android:requiresFadingEdge="vertical"
+            android:fadingEdgeLength="30dp">
 
-        <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-            android:id="@+id/tv_pip_menu_close_button"
-            android:layout_width="@dimen/picture_in_picture_button_width"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
-            android:src="@drawable/pip_ic_close_white"
-            android:text="@string/pip_close" />
+            <LinearLayout
+                android:id="@+id/tv_pip_menu_action_buttons"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
+                android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
+                android:gravity="center"
+                android:orientation="horizontal">
 
-        <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_fullscreen_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_fullscreen_white"
+                    android:text="@string/pip_fullscreen" />
 
-    </LinearLayout>
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_close_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_close_white"
+                    android:text="@string/pip_close" />
 
+                <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+            </LinearLayout>
+        </HorizontalScrollView>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/tv_pip_menu_border"/>
+    </FrameLayout>
 </FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
index 5925008..f9d0968 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
@@ -15,36 +15,20 @@
     limitations under the License.
 -->
 <!-- Layout for TvPipMenuActionButton -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/button"
+        android:layout_width="@dimen/pip_menu_button_size"
+        android:layout_height="@dimen/pip_menu_button_size"
+        android:layout_marginStart="@dimen/pip_menu_button_margin"
+        android:layout_marginEnd="@dimen/pip_menu_button_margin"
+        android:background="@drawable/tv_pip_button_bg"
+        android:focusable="true">
 
-    <ImageView android:id="@+id/button"
-        android:layout_width="34dp"
-        android:layout_height="34dp"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
-        android:focusable="true"
-        android:src="@drawable/tv_pip_button_focused"
-        android:importantForAccessibility="yes" />
-
-    <ImageView android:id="@+id/icon"
-        android:layout_width="34dp"
-        android:layout_height="34dp"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
-        android:padding="5dp"
-        android:importantForAccessibility="no" />
-
-    <TextView android:id="@+id/desc"
-        android:layout_width="100dp"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/icon"
-        android:layout_centerHorizontal="true"
-        android:layout_marginTop="3dp"
-        android:gravity="center"
-        android:text="@string/pip_fullscreen"
-        android:alpha="0"
-        android:fontFamily="sans-serif"
-        android:textSize="12sp"
-        android:textColor="#EEEEEE"
-        android:importantForAccessibility="no" />
-</merge>
+        <ImageView android:id="@+id/icon"
+                   android:layout_width="@dimen/pip_menu_icon_size"
+                   android:layout_height="@dimen/pip_menu_icon_size"
+                   android:layout_gravity="center"
+                   android:duplicateParentState="true"
+                   android:tint="@color/tv_pip_menu_icon" />
+</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
deleted file mode 100644
index bf4eb26..0000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2020 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<com.android.wm.shell.pip.tv.TvPipMenuActionButton
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/picture_in_picture_button_width"
-    android:layout_height="wrap_content"
-    android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 7920fd2..e41ebc4 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -16,7 +16,12 @@
 -->
 <resources>
     <!-- The dimensions to user for picture-in-picture action buttons. -->
-    <dimen name="picture_in_picture_button_width">100dp</dimen>
-    <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
+    <dimen name="pip_menu_button_size">40dp</dimen>
+    <dimen name="pip_menu_button_radius">20dp</dimen>
+    <dimen name="pip_menu_icon_size">20dp</dimen>
+    <dimen name="pip_menu_button_margin">4dp</dimen>
+    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+    <dimen name="pip_menu_border_width">2dp</dimen>
+    <dimen name="pip_menu_border_radius">0dp</dimen>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
new file mode 100644
index 0000000..17387fa
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
+    <color name="tv_pip_menu_icon_unfocused">#E8EAED</color>
+    <color name="tv_pip_menu_icon_disabled">#80868B</color>
+    <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
+    <color name="tv_pip_menu_icon_bg_unfocused">#777777</color>
+    <color name="tv_pip_menu_focus_border">#CCE8EAED</color>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 8467cc5..92a3598 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -122,7 +122,7 @@
 
         if (mTargetViewContainer != null) {
             // init can be called multiple times, remove the old one from view hierarchy first.
-            mWindowManager.removeViewImmediate(mTargetViewContainer);
+            cleanUpDismissTarget();
         }
 
         mTargetView = new DismissCircleView(mContext);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 83390a5..b165706 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -253,9 +253,6 @@
         final Rect newBounds;
         switch (mState) {
             case STATE_PIP_MENU:
-                newBounds = mPipBoundsState.getExpandedBounds();
-                break;
-
             case STATE_PIP:
                 // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
                 // Internally, it will get the "default" bounds from PipBoundsState and adjust them
@@ -336,11 +333,6 @@
     private void loadConfigurations() {
         final Resources res = mContext.getResources();
         mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
-        // "Cache" bounds for the Pip menu as "expanded" bounds in PipBoundsState. We'll refer back
-        // to this value in resizePinnedStack(), when we are adjusting Pip task/window position for
-        // the menu.
-        mPipBoundsState.setExpandedBounds(
-                Rect.unflattenFromString(res.getString(R.string.pip_menu_bounds)));
     }
 
     private DisplayInfo getDisplayInfo() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
index 6f7cd82..bda685e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.pip.tv;
 
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
@@ -26,7 +24,6 @@
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
-import android.widget.TextView;
 
 import com.android.wm.shell.R;
 
@@ -36,12 +33,7 @@
  */
 public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
     private final ImageView mIconImageView;
-    private final ImageView mButtonImageView;
-    private final TextView mDescriptionTextView;
-    private Animator mTextFocusGainAnimator;
-    private Animator mButtonFocusGainAnimator;
-    private Animator mTextFocusLossAnimator;
-    private Animator mButtonFocusLossAnimator;
+    private final View mButtonView;
     private OnClickListener mOnClickListener;
 
     public TvPipMenuActionButton(Context context) {
@@ -64,8 +56,7 @@
         inflater.inflate(R.layout.tv_pip_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
-        mButtonImageView = findViewById(R.id.button);
-        mDescriptionTextView = findViewById(R.id.desc);
+        mButtonView = findViewById(R.id.button);
 
         final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
         final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
@@ -76,43 +67,16 @@
         if (textResId != 0) {
             setTextAndDescription(getContext().getString(textResId));
         }
-
         typedArray.recycle();
     }
 
     @Override
-    public void onFinishInflate() {
-        super.onFinishInflate();
-        mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> {
-            if (hasFocus) {
-                startFocusGainAnimation();
-            } else {
-                startFocusLossAnimation();
-            }
-        });
-
-        mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_gain_animation);
-        mTextFocusGainAnimator.setTarget(mDescriptionTextView);
-        mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_gain_animation);
-        mButtonFocusGainAnimator.setTarget(mButtonImageView);
-
-        mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_loss_animation);
-        mTextFocusLossAnimator.setTarget(mDescriptionTextView);
-        mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_loss_animation);
-        mButtonFocusLossAnimator.setTarget(mButtonImageView);
-    }
-
-    @Override
     public void setOnClickListener(OnClickListener listener) {
         // We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to
         // the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
         // listener to the ImageView.
         mOnClickListener = listener;
-        mButtonImageView.setOnClickListener(listener != null ? this : null);
+        mButtonView.setOnClickListener(listener != null ? this : null);
     }
 
     @Override
@@ -143,55 +107,16 @@
      * Sets the text for description the with the given string.
      */
     public void setTextAndDescription(CharSequence text) {
-        mButtonImageView.setContentDescription(text);
-        mDescriptionTextView.setText(text);
+        mButtonView.setContentDescription(text);
     }
 
-    private static void cancelAnimator(Animator animator) {
-        if (animator.isStarted()) {
-            animator.cancel();
-        }
+    @Override
+    public void setEnabled(boolean enabled) {
+        mButtonView.setEnabled(enabled);
     }
 
-    /**
-     * Starts the focus gain animation.
-     */
-    public void startFocusGainAnimation() {
-        cancelAnimator(mButtonFocusLossAnimator);
-        cancelAnimator(mTextFocusLossAnimator);
-        mTextFocusGainAnimator.start();
-        if (mButtonImageView.getAlpha() < 1f) {
-            // If we had faded out the ripple drawable, run our manual focus change animation.
-            // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
-            // animator.
-            mButtonFocusGainAnimator.start();
-        }
-    }
-
-    /**
-     * Starts the focus loss animation.
-     */
-    public void startFocusLossAnimation() {
-        cancelAnimator(mButtonFocusGainAnimator);
-        cancelAnimator(mTextFocusGainAnimator);
-        mTextFocusLossAnimator.start();
-        if (mButtonImageView.hasFocus()) {
-            // Button uses ripple that has the default animation for the focus changes.
-            // However, it doesn't expose the API to fade out while it is focused, so we should
-            // manually run the fade out animation when PIP controls row loses focus.
-            mButtonFocusLossAnimator.start();
-        }
-    }
-
-    /**
-     * Resets to initial state.
-     */
-    public void reset() {
-        cancelAnimator(mButtonFocusGainAnimator);
-        cancelAnimator(mTextFocusGainAnimator);
-        cancelAnimator(mButtonFocusLossAnimator);
-        cancelAnimator(mTextFocusLossAnimator);
-        mButtonImageView.setAlpha(1f);
-        mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
+    @Override
+    public boolean isEnabled() {
+        return mButtonView.isEnabled();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ee41b41..77bfa07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.util.Log;
 import android.view.SurfaceControl;
@@ -122,19 +123,19 @@
         if (DEBUG) Log.d(TAG, "showMenu()");
 
         if (mMenuView != null) {
-            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
-                    mPipBoundsState.getDisplayBounds().width(),
-                    mPipBoundsState.getDisplayBounds().height()));
+            Rect pipBounds = mPipBoundsState.getBounds();
+            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(
+                    MENU_WINDOW_TITLE, pipBounds.width(), pipBounds.height()));
             maybeUpdateMenuViewActions();
-            mMenuView.show();
 
-            // By default, SystemWindows views are above everything else.
-            // Set the relative z-order so the menu is below PiP.
-            if (mMenuView.getWindowSurfaceControl() != null && mLeash != null) {
+            SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mMenuView);
+            if (menuSurfaceControl != null) {
                 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, -1);
+                t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, 1);
+                t.setPosition(menuSurfaceControl, pipBounds.left, pipBounds.top);
                 t.apply();
             }
+            mMenuView.show();
         }
     }
 
@@ -181,7 +182,15 @@
 
     private void onMediaActionsChanged(List<RemoteAction> actions) {
         if (DEBUG) Log.d(TAG, "onMediaActionsChanged()");
-        updateAdditionalActionsList(mMediaActions, actions);
+
+        // Hide disabled actions.
+        List<RemoteAction> enabledActions = new ArrayList<>();
+        for (RemoteAction remoteAction : actions) {
+            if (remoteAction.isEnabled()) {
+                enabledActions.add(remoteAction);
+            }
+        }
+        updateAdditionalActionsList(mMediaActions, enabledActions);
     }
 
     private void updateAdditionalActionsList(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index d6cd9ea..4327f15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -24,9 +24,7 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
-import android.graphics.Color;
 import android.os.Handler;
-import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -55,13 +53,13 @@
     private static final String TAG = "TvPipMenuView";
     private static final boolean DEBUG = TvPipController.DEBUG;
 
-    private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
     private final Animator mFadeInAnimation;
     private final Animator mFadeOutAnimation;
-    @Nullable private Listener mListener;
+    @Nullable
+    private Listener mListener;
 
     private final LinearLayout mActionButtonsContainer;
+    private final View mMenuFrameView;
     private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
 
     public TvPipMenuView(@NonNull Context context) {
@@ -88,11 +86,12 @@
         mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
                 .setOnClickListener(this);
 
+        mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
         mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
-        mFadeInAnimation.setTarget(mActionButtonsContainer);
+        mFadeInAnimation.setTarget(mMenuFrameView);
 
         mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
-        mFadeOutAnimation.setTarget(mActionButtonsContainer);
+        mFadeOutAnimation.setTarget(mMenuFrameView);
     }
 
     void setListener(@Nullable Listener listener) {
@@ -103,7 +102,6 @@
         if (DEBUG) Log.d(TAG, "show()");
 
         mFadeInAnimation.start();
-        setAlpha(1.0f);
         grantWindowFocus(true);
     }
 
@@ -111,12 +109,11 @@
         if (DEBUG) Log.d(TAG, "hide()");
 
         mFadeOutAnimation.start();
-        setAlpha(0.0f);
         grantWindowFocus(false);
     }
 
     boolean isVisible() {
-        return getAlpha() == 1.0f;
+        return mMenuFrameView != null && mMenuFrameView.getAlpha() != 0.0f;
     }
 
     private void grantWindowFocus(boolean grantFocus) {
@@ -140,9 +137,7 @@
             final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
             // Add buttons until we have enough to display all of the actions.
             while (actionsNumber > buttonsNumber) {
-                final TvPipMenuActionButton button = (TvPipMenuActionButton) layoutInflater.inflate(
-                        R.layout.tv_pip_menu_additional_action_button, mActionButtonsContainer,
-                        false);
+                TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
                 button.setOnClickListener(this);
 
                 mActionButtonsContainer.addView(button);
@@ -168,13 +163,8 @@
             button.setVisibility(View.VISIBLE); // Ensure the button is visible.
             button.setTextAndDescription(action.getContentDescription());
             button.setEnabled(action.isEnabled());
-            button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
             button.setTag(action);
-
-            action.getIcon().loadDrawableAsync(mContext, drawable -> {
-                drawable.setTint(Color.WHITE);
-                button.setImageDrawable(drawable);
-            }, mainHandler);
+            action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a0d9d03..e404724 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -346,6 +346,15 @@
             }
 
             if (change.getMode() == TRANSIT_CHANGE) {
+                // If task is child task, only set position in parent.
+                if (isTask && change.getParent() != null
+                        && info.getChange(change.getParent()).getTaskInfo() != null) {
+                    final Point positionInParent = change.getTaskInfo().positionInParent;
+                    startTransaction.setPosition(change.getLeash(),
+                            positionInParent.x, positionInParent.y);
+                    continue;
+                }
+
                 // No default animation for this, so just update bounds/position.
                 startTransaction.setPosition(change.getLeash(),
                         change.getEndAbsBounds().left - change.getEndRelOffset().x,
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index 2c3567a..2c005fd 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -34,206 +34,209 @@
     /* 30 */ {'H', 'a', 'n', 't'},
     /* 31 */ {'H', 'e', 'b', 'r'},
     /* 32 */ {'H', 'l', 'u', 'w'},
-    /* 33 */ {'H', 'm', 'n', 'g'},
-    /* 34 */ {'H', 'm', 'n', 'p'},
-    /* 35 */ {'I', 't', 'a', 'l'},
-    /* 36 */ {'J', 'p', 'a', 'n'},
-    /* 37 */ {'K', 'a', 'l', 'i'},
-    /* 38 */ {'K', 'a', 'n', 'a'},
-    /* 39 */ {'K', 'h', 'a', 'r'},
-    /* 40 */ {'K', 'h', 'm', 'r'},
-    /* 41 */ {'K', 'i', 't', 's'},
-    /* 42 */ {'K', 'n', 'd', 'a'},
-    /* 43 */ {'K', 'o', 'r', 'e'},
-    /* 44 */ {'L', 'a', 'n', 'a'},
-    /* 45 */ {'L', 'a', 'o', 'o'},
-    /* 46 */ {'L', 'a', 't', 'n'},
-    /* 47 */ {'L', 'e', 'p', 'c'},
-    /* 48 */ {'L', 'i', 'n', 'a'},
-    /* 49 */ {'L', 'i', 's', 'u'},
-    /* 50 */ {'L', 'y', 'c', 'i'},
-    /* 51 */ {'L', 'y', 'd', 'i'},
-    /* 52 */ {'M', 'a', 'n', 'd'},
-    /* 53 */ {'M', 'a', 'n', 'i'},
-    /* 54 */ {'M', 'e', 'd', 'f'},
-    /* 55 */ {'M', 'e', 'r', 'c'},
-    /* 56 */ {'M', 'l', 'y', 'm'},
-    /* 57 */ {'M', 'o', 'n', 'g'},
-    /* 58 */ {'M', 'r', 'o', 'o'},
-    /* 59 */ {'M', 'y', 'm', 'r'},
-    /* 60 */ {'N', 'a', 'r', 'b'},
-    /* 61 */ {'N', 'k', 'o', 'o'},
-    /* 62 */ {'N', 's', 'h', 'u'},
-    /* 63 */ {'O', 'g', 'a', 'm'},
-    /* 64 */ {'O', 'l', 'c', 'k'},
-    /* 65 */ {'O', 'r', 'k', 'h'},
-    /* 66 */ {'O', 'r', 'y', 'a'},
-    /* 67 */ {'O', 's', 'g', 'e'},
+    /* 33 */ {'H', 'm', 'n', 'p'},
+    /* 34 */ {'I', 't', 'a', 'l'},
+    /* 35 */ {'J', 'p', 'a', 'n'},
+    /* 36 */ {'K', 'a', 'l', 'i'},
+    /* 37 */ {'K', 'a', 'n', 'a'},
+    /* 38 */ {'K', 'h', 'a', 'r'},
+    /* 39 */ {'K', 'h', 'm', 'r'},
+    /* 40 */ {'K', 'i', 't', 's'},
+    /* 41 */ {'K', 'n', 'd', 'a'},
+    /* 42 */ {'K', 'o', 'r', 'e'},
+    /* 43 */ {'L', 'a', 'n', 'a'},
+    /* 44 */ {'L', 'a', 'o', 'o'},
+    /* 45 */ {'L', 'a', 't', 'n'},
+    /* 46 */ {'L', 'e', 'p', 'c'},
+    /* 47 */ {'L', 'i', 'n', 'a'},
+    /* 48 */ {'L', 'i', 's', 'u'},
+    /* 49 */ {'L', 'y', 'c', 'i'},
+    /* 50 */ {'L', 'y', 'd', 'i'},
+    /* 51 */ {'M', 'a', 'n', 'd'},
+    /* 52 */ {'M', 'a', 'n', 'i'},
+    /* 53 */ {'M', 'e', 'd', 'f'},
+    /* 54 */ {'M', 'e', 'r', 'c'},
+    /* 55 */ {'M', 'l', 'y', 'm'},
+    /* 56 */ {'M', 'o', 'n', 'g'},
+    /* 57 */ {'M', 'r', 'o', 'o'},
+    /* 58 */ {'M', 'y', 'm', 'r'},
+    /* 59 */ {'N', 'a', 'r', 'b'},
+    /* 60 */ {'N', 'k', 'o', 'o'},
+    /* 61 */ {'N', 's', 'h', 'u'},
+    /* 62 */ {'O', 'g', 'a', 'm'},
+    /* 63 */ {'O', 'l', 'c', 'k'},
+    /* 64 */ {'O', 'r', 'k', 'h'},
+    /* 65 */ {'O', 'r', 'y', 'a'},
+    /* 66 */ {'O', 's', 'g', 'e'},
+    /* 67 */ {'O', 'u', 'g', 'r'},
     /* 68 */ {'P', 'a', 'u', 'c'},
     /* 69 */ {'P', 'h', 'l', 'i'},
     /* 70 */ {'P', 'h', 'n', 'x'},
     /* 71 */ {'P', 'l', 'r', 'd'},
     /* 72 */ {'P', 'r', 't', 'i'},
-    /* 73 */ {'R', 'u', 'n', 'r'},
-    /* 74 */ {'S', 'a', 'm', 'r'},
-    /* 75 */ {'S', 'a', 'r', 'b'},
-    /* 76 */ {'S', 'a', 'u', 'r'},
-    /* 77 */ {'S', 'g', 'n', 'w'},
-    /* 78 */ {'S', 'i', 'n', 'h'},
-    /* 79 */ {'S', 'o', 'g', 'd'},
-    /* 80 */ {'S', 'o', 'r', 'a'},
-    /* 81 */ {'S', 'o', 'y', 'o'},
-    /* 82 */ {'S', 'y', 'r', 'c'},
-    /* 83 */ {'T', 'a', 'l', 'e'},
-    /* 84 */ {'T', 'a', 'l', 'u'},
-    /* 85 */ {'T', 'a', 'm', 'l'},
-    /* 86 */ {'T', 'a', 'n', 'g'},
-    /* 87 */ {'T', 'a', 'v', 't'},
-    /* 88 */ {'T', 'e', 'l', 'u'},
-    /* 89 */ {'T', 'f', 'n', 'g'},
-    /* 90 */ {'T', 'h', 'a', 'a'},
-    /* 91 */ {'T', 'h', 'a', 'i'},
-    /* 92 */ {'T', 'i', 'b', 't'},
-    /* 93 */ {'U', 'g', 'a', 'r'},
-    /* 94 */ {'V', 'a', 'i', 'i'},
-    /* 95 */ {'W', 'c', 'h', 'o'},
-    /* 96 */ {'X', 'p', 'e', 'o'},
-    /* 97 */ {'X', 's', 'u', 'x'},
-    /* 98 */ {'Y', 'i', 'i', 'i'},
-    /* 99 */ {'~', '~', '~', 'A'},
-    /* 100 */ {'~', '~', '~', 'B'},
+    /* 73 */ {'R', 'o', 'h', 'g'},
+    /* 74 */ {'R', 'u', 'n', 'r'},
+    /* 75 */ {'S', 'a', 'm', 'r'},
+    /* 76 */ {'S', 'a', 'r', 'b'},
+    /* 77 */ {'S', 'a', 'u', 'r'},
+    /* 78 */ {'S', 'g', 'n', 'w'},
+    /* 79 */ {'S', 'i', 'n', 'h'},
+    /* 80 */ {'S', 'o', 'g', 'd'},
+    /* 81 */ {'S', 'o', 'r', 'a'},
+    /* 82 */ {'S', 'o', 'y', 'o'},
+    /* 83 */ {'S', 'y', 'r', 'c'},
+    /* 84 */ {'T', 'a', 'l', 'e'},
+    /* 85 */ {'T', 'a', 'l', 'u'},
+    /* 86 */ {'T', 'a', 'm', 'l'},
+    /* 87 */ {'T', 'a', 'n', 'g'},
+    /* 88 */ {'T', 'a', 'v', 't'},
+    /* 89 */ {'T', 'e', 'l', 'u'},
+    /* 90 */ {'T', 'f', 'n', 'g'},
+    /* 91 */ {'T', 'h', 'a', 'a'},
+    /* 92 */ {'T', 'h', 'a', 'i'},
+    /* 93 */ {'T', 'i', 'b', 't'},
+    /* 94 */ {'T', 'n', 's', 'a'},
+    /* 95 */ {'T', 'o', 't', 'o'},
+    /* 96 */ {'U', 'g', 'a', 'r'},
+    /* 97 */ {'V', 'a', 'i', 'i'},
+    /* 98 */ {'W', 'c', 'h', 'o'},
+    /* 99 */ {'X', 'p', 'e', 'o'},
+    /* 100 */ {'X', 's', 'u', 'x'},
+    /* 101 */ {'Y', 'i', 'i', 'i'},
+    /* 102 */ {'~', '~', '~', 'A'},
+    /* 103 */ {'~', '~', '~', 'B'},
 };
 
 
 const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
-    {0x61610000u, 46u}, // aa -> Latn
-    {0xA0000000u, 46u}, // aai -> Latn
-    {0xA8000000u, 46u}, // aak -> Latn
-    {0xD0000000u, 46u}, // aau -> Latn
+    {0x61610000u, 45u}, // aa -> Latn
+    {0xA0000000u, 45u}, // aai -> Latn
+    {0xA8000000u, 45u}, // aak -> Latn
+    {0xD0000000u, 45u}, // aau -> Latn
     {0x61620000u, 18u}, // ab -> Cyrl
-    {0xA0200000u, 46u}, // abi -> Latn
+    {0xA0200000u, 45u}, // abi -> Latn
     {0xC0200000u, 18u}, // abq -> Cyrl
-    {0xC4200000u, 46u}, // abr -> Latn
-    {0xCC200000u, 46u}, // abt -> Latn
-    {0xE0200000u, 46u}, // aby -> Latn
-    {0x8C400000u, 46u}, // acd -> Latn
-    {0x90400000u, 46u}, // ace -> Latn
-    {0x9C400000u, 46u}, // ach -> Latn
-    {0x80600000u, 46u}, // ada -> Latn
-    {0x90600000u, 46u}, // ade -> Latn
-    {0xA4600000u, 46u}, // adj -> Latn
-    {0xBC600000u, 92u}, // adp -> Tibt
+    {0xC4200000u, 45u}, // abr -> Latn
+    {0xCC200000u, 45u}, // abt -> Latn
+    {0xE0200000u, 45u}, // aby -> Latn
+    {0x8C400000u, 45u}, // acd -> Latn
+    {0x90400000u, 45u}, // ace -> Latn
+    {0x9C400000u, 45u}, // ach -> Latn
+    {0x80600000u, 45u}, // ada -> Latn
+    {0x90600000u, 45u}, // ade -> Latn
+    {0xA4600000u, 45u}, // adj -> Latn
+    {0xBC600000u, 93u}, // adp -> Tibt
     {0xE0600000u, 18u}, // ady -> Cyrl
-    {0xE4600000u, 46u}, // adz -> Latn
+    {0xE4600000u, 45u}, // adz -> Latn
     {0x61650000u,  5u}, // ae -> Avst
     {0x84800000u,  2u}, // aeb -> Arab
-    {0xE0800000u, 46u}, // aey -> Latn
-    {0x61660000u, 46u}, // af -> Latn
-    {0x88C00000u, 46u}, // agc -> Latn
-    {0x8CC00000u, 46u}, // agd -> Latn
-    {0x98C00000u, 46u}, // agg -> Latn
-    {0xB0C00000u, 46u}, // agm -> Latn
-    {0xB8C00000u, 46u}, // ago -> Latn
-    {0xC0C00000u, 46u}, // agq -> Latn
-    {0x80E00000u, 46u}, // aha -> Latn
-    {0xACE00000u, 46u}, // ahl -> Latn
+    {0xE0800000u, 45u}, // aey -> Latn
+    {0x61660000u, 45u}, // af -> Latn
+    {0x88C00000u, 45u}, // agc -> Latn
+    {0x8CC00000u, 45u}, // agd -> Latn
+    {0x98C00000u, 45u}, // agg -> Latn
+    {0xB0C00000u, 45u}, // agm -> Latn
+    {0xB8C00000u, 45u}, // ago -> Latn
+    {0xC0C00000u, 45u}, // agq -> Latn
+    {0x80E00000u, 45u}, // aha -> Latn
+    {0xACE00000u, 45u}, // ahl -> Latn
     {0xB8E00000u,  1u}, // aho -> Ahom
-    {0x99200000u, 46u}, // ajg -> Latn
-    {0x616B0000u, 46u}, // ak -> Latn
-    {0xA9400000u, 97u}, // akk -> Xsux
-    {0x81600000u, 46u}, // ala -> Latn
-    {0xA1600000u, 46u}, // ali -> Latn
-    {0xB5600000u, 46u}, // aln -> Latn
+    {0x99200000u, 45u}, // ajg -> Latn
+    {0x616B0000u, 45u}, // ak -> Latn
+    {0xA9400000u, 100u}, // akk -> Xsux
+    {0x81600000u, 45u}, // ala -> Latn
+    {0xA1600000u, 45u}, // ali -> Latn
+    {0xB5600000u, 45u}, // aln -> Latn
     {0xCD600000u, 18u}, // alt -> Cyrl
     {0x616D0000u, 21u}, // am -> Ethi
-    {0xB1800000u, 46u}, // amm -> Latn
-    {0xB5800000u, 46u}, // amn -> Latn
-    {0xB9800000u, 46u}, // amo -> Latn
-    {0xBD800000u, 46u}, // amp -> Latn
-    {0x616E0000u, 46u}, // an -> Latn
-    {0x89A00000u, 46u}, // anc -> Latn
-    {0xA9A00000u, 46u}, // ank -> Latn
-    {0xB5A00000u, 46u}, // ann -> Latn
-    {0xE1A00000u, 46u}, // any -> Latn
-    {0xA5C00000u, 46u}, // aoj -> Latn
-    {0xB1C00000u, 46u}, // aom -> Latn
-    {0xE5C00000u, 46u}, // aoz -> Latn
+    {0xB1800000u, 45u}, // amm -> Latn
+    {0xB5800000u, 45u}, // amn -> Latn
+    {0xB9800000u, 45u}, // amo -> Latn
+    {0xBD800000u, 45u}, // amp -> Latn
+    {0x616E0000u, 45u}, // an -> Latn
+    {0x89A00000u, 45u}, // anc -> Latn
+    {0xA9A00000u, 45u}, // ank -> Latn
+    {0xB5A00000u, 45u}, // ann -> Latn
+    {0xE1A00000u, 45u}, // any -> Latn
+    {0xA5C00000u, 45u}, // aoj -> Latn
+    {0xB1C00000u, 45u}, // aom -> Latn
+    {0xE5C00000u, 45u}, // aoz -> Latn
     {0x89E00000u,  2u}, // apc -> Arab
     {0x8DE00000u,  2u}, // apd -> Arab
-    {0x91E00000u, 46u}, // ape -> Latn
-    {0xC5E00000u, 46u}, // apr -> Latn
-    {0xC9E00000u, 46u}, // aps -> Latn
-    {0xE5E00000u, 46u}, // apz -> Latn
+    {0x91E00000u, 45u}, // ape -> Latn
+    {0xC5E00000u, 45u}, // apr -> Latn
+    {0xC9E00000u, 45u}, // aps -> Latn
+    {0xE5E00000u, 45u}, // apz -> Latn
     {0x61720000u,  2u}, // ar -> Arab
-    {0x61725842u, 100u}, // ar-XB -> ~~~B
+    {0x61725842u, 103u}, // ar-XB -> ~~~B
     {0x8A200000u,  3u}, // arc -> Armi
-    {0x9E200000u, 46u}, // arh -> Latn
-    {0xB6200000u, 46u}, // arn -> Latn
-    {0xBA200000u, 46u}, // aro -> Latn
+    {0x9E200000u, 45u}, // arh -> Latn
+    {0xB6200000u, 45u}, // arn -> Latn
+    {0xBA200000u, 45u}, // aro -> Latn
     {0xC2200000u,  2u}, // arq -> Arab
     {0xCA200000u,  2u}, // ars -> Arab
     {0xE2200000u,  2u}, // ary -> Arab
     {0xE6200000u,  2u}, // arz -> Arab
     {0x61730000u,  8u}, // as -> Beng
-    {0x82400000u, 46u}, // asa -> Latn
-    {0x92400000u, 77u}, // ase -> Sgnw
-    {0x9A400000u, 46u}, // asg -> Latn
-    {0xBA400000u, 46u}, // aso -> Latn
-    {0xCE400000u, 46u}, // ast -> Latn
-    {0x82600000u, 46u}, // ata -> Latn
-    {0x9A600000u, 46u}, // atg -> Latn
-    {0xA6600000u, 46u}, // atj -> Latn
-    {0xE2800000u, 46u}, // auy -> Latn
+    {0x82400000u, 45u}, // asa -> Latn
+    {0x92400000u, 78u}, // ase -> Sgnw
+    {0x9A400000u, 45u}, // asg -> Latn
+    {0xBA400000u, 45u}, // aso -> Latn
+    {0xCE400000u, 45u}, // ast -> Latn
+    {0x82600000u, 45u}, // ata -> Latn
+    {0x9A600000u, 45u}, // atg -> Latn
+    {0xA6600000u, 45u}, // atj -> Latn
+    {0xE2800000u, 45u}, // auy -> Latn
     {0x61760000u, 18u}, // av -> Cyrl
     {0xAEA00000u,  2u}, // avl -> Arab
-    {0xB6A00000u, 46u}, // avn -> Latn
-    {0xCEA00000u, 46u}, // avt -> Latn
-    {0xD2A00000u, 46u}, // avu -> Latn
+    {0xB6A00000u, 45u}, // avn -> Latn
+    {0xCEA00000u, 45u}, // avt -> Latn
+    {0xD2A00000u, 45u}, // avu -> Latn
     {0x82C00000u, 19u}, // awa -> Deva
-    {0x86C00000u, 46u}, // awb -> Latn
-    {0xBAC00000u, 46u}, // awo -> Latn
-    {0xDEC00000u, 46u}, // awx -> Latn
-    {0x61790000u, 46u}, // ay -> Latn
-    {0x87000000u, 46u}, // ayb -> Latn
-    {0x617A0000u, 46u}, // az -> Latn
+    {0x86C00000u, 45u}, // awb -> Latn
+    {0xBAC00000u, 45u}, // awo -> Latn
+    {0xDEC00000u, 45u}, // awx -> Latn
+    {0x61790000u, 45u}, // ay -> Latn
+    {0x87000000u, 45u}, // ayb -> Latn
+    {0x617A0000u, 45u}, // az -> Latn
     {0x617A4951u,  2u}, // az-IQ -> Arab
     {0x617A4952u,  2u}, // az-IR -> Arab
     {0x617A5255u, 18u}, // az-RU -> Cyrl
     {0x62610000u, 18u}, // ba -> Cyrl
     {0xAC010000u,  2u}, // bal -> Arab
-    {0xB4010000u, 46u}, // ban -> Latn
+    {0xB4010000u, 45u}, // ban -> Latn
     {0xBC010000u, 19u}, // bap -> Deva
-    {0xC4010000u, 46u}, // bar -> Latn
-    {0xC8010000u, 46u}, // bas -> Latn
-    {0xD4010000u, 46u}, // bav -> Latn
+    {0xC4010000u, 45u}, // bar -> Latn
+    {0xC8010000u, 45u}, // bas -> Latn
+    {0xD4010000u, 45u}, // bav -> Latn
     {0xDC010000u,  6u}, // bax -> Bamu
-    {0x80210000u, 46u}, // bba -> Latn
-    {0x84210000u, 46u}, // bbb -> Latn
-    {0x88210000u, 46u}, // bbc -> Latn
-    {0x8C210000u, 46u}, // bbd -> Latn
-    {0xA4210000u, 46u}, // bbj -> Latn
-    {0xBC210000u, 46u}, // bbp -> Latn
-    {0xC4210000u, 46u}, // bbr -> Latn
-    {0x94410000u, 46u}, // bcf -> Latn
-    {0x9C410000u, 46u}, // bch -> Latn
-    {0xA0410000u, 46u}, // bci -> Latn
-    {0xB0410000u, 46u}, // bcm -> Latn
-    {0xB4410000u, 46u}, // bcn -> Latn
-    {0xB8410000u, 46u}, // bco -> Latn
+    {0x80210000u, 45u}, // bba -> Latn
+    {0x84210000u, 45u}, // bbb -> Latn
+    {0x88210000u, 45u}, // bbc -> Latn
+    {0x8C210000u, 45u}, // bbd -> Latn
+    {0xA4210000u, 45u}, // bbj -> Latn
+    {0xBC210000u, 45u}, // bbp -> Latn
+    {0xC4210000u, 45u}, // bbr -> Latn
+    {0x94410000u, 45u}, // bcf -> Latn
+    {0x9C410000u, 45u}, // bch -> Latn
+    {0xA0410000u, 45u}, // bci -> Latn
+    {0xB0410000u, 45u}, // bcm -> Latn
+    {0xB4410000u, 45u}, // bcn -> Latn
+    {0xB8410000u, 45u}, // bco -> Latn
     {0xC0410000u, 21u}, // bcq -> Ethi
-    {0xD0410000u, 46u}, // bcu -> Latn
-    {0x8C610000u, 46u}, // bdd -> Latn
+    {0xD0410000u, 45u}, // bcu -> Latn
+    {0x8C610000u, 45u}, // bdd -> Latn
     {0x62650000u, 18u}, // be -> Cyrl
-    {0x94810000u, 46u}, // bef -> Latn
-    {0x9C810000u, 46u}, // beh -> Latn
+    {0x94810000u, 45u}, // bef -> Latn
+    {0x9C810000u, 45u}, // beh -> Latn
     {0xA4810000u,  2u}, // bej -> Arab
-    {0xB0810000u, 46u}, // bem -> Latn
-    {0xCC810000u, 46u}, // bet -> Latn
-    {0xD8810000u, 46u}, // bew -> Latn
-    {0xDC810000u, 46u}, // bex -> Latn
-    {0xE4810000u, 46u}, // bez -> Latn
-    {0x8CA10000u, 46u}, // bfd -> Latn
-    {0xC0A10000u, 85u}, // bfq -> Taml
+    {0xB0810000u, 45u}, // bem -> Latn
+    {0xCC810000u, 45u}, // bet -> Latn
+    {0xD8810000u, 45u}, // bew -> Latn
+    {0xDC810000u, 45u}, // bex -> Latn
+    {0xE4810000u, 45u}, // bez -> Latn
+    {0x8CA10000u, 45u}, // bfd -> Latn
+    {0xC0A10000u, 86u}, // bfq -> Taml
     {0xCCA10000u,  2u}, // bft -> Arab
     {0xE0A10000u, 19u}, // bfy -> Deva
     {0x62670000u, 18u}, // bg -> Cyrl
@@ -241,1235 +244,1239 @@
     {0xB4C10000u,  2u}, // bgn -> Arab
     {0xDCC10000u, 26u}, // bgx -> Grek
     {0x84E10000u, 19u}, // bhb -> Deva
-    {0x98E10000u, 46u}, // bhg -> Latn
+    {0x98E10000u, 45u}, // bhg -> Latn
     {0xA0E10000u, 19u}, // bhi -> Deva
-    {0xACE10000u, 46u}, // bhl -> Latn
+    {0xACE10000u, 45u}, // bhl -> Latn
     {0xB8E10000u, 19u}, // bho -> Deva
-    {0xE0E10000u, 46u}, // bhy -> Latn
-    {0x62690000u, 46u}, // bi -> Latn
-    {0x85010000u, 46u}, // bib -> Latn
-    {0x99010000u, 46u}, // big -> Latn
-    {0xA9010000u, 46u}, // bik -> Latn
-    {0xB1010000u, 46u}, // bim -> Latn
-    {0xB5010000u, 46u}, // bin -> Latn
-    {0xB9010000u, 46u}, // bio -> Latn
-    {0xC1010000u, 46u}, // biq -> Latn
-    {0x9D210000u, 46u}, // bjh -> Latn
+    {0xE0E10000u, 45u}, // bhy -> Latn
+    {0x62690000u, 45u}, // bi -> Latn
+    {0x85010000u, 45u}, // bib -> Latn
+    {0x99010000u, 45u}, // big -> Latn
+    {0xA9010000u, 45u}, // bik -> Latn
+    {0xB1010000u, 45u}, // bim -> Latn
+    {0xB5010000u, 45u}, // bin -> Latn
+    {0xB9010000u, 45u}, // bio -> Latn
+    {0xC1010000u, 45u}, // biq -> Latn
+    {0x9D210000u, 45u}, // bjh -> Latn
     {0xA1210000u, 21u}, // bji -> Ethi
     {0xA5210000u, 19u}, // bjj -> Deva
-    {0xB5210000u, 46u}, // bjn -> Latn
-    {0xB9210000u, 46u}, // bjo -> Latn
-    {0xC5210000u, 46u}, // bjr -> Latn
-    {0xCD210000u, 46u}, // bjt -> Latn
-    {0xE5210000u, 46u}, // bjz -> Latn
-    {0x89410000u, 46u}, // bkc -> Latn
-    {0xB1410000u, 46u}, // bkm -> Latn
-    {0xC1410000u, 46u}, // bkq -> Latn
-    {0xD1410000u, 46u}, // bku -> Latn
-    {0xD5410000u, 46u}, // bkv -> Latn
-    {0xCD610000u, 87u}, // blt -> Tavt
-    {0x626D0000u, 46u}, // bm -> Latn
-    {0x9D810000u, 46u}, // bmh -> Latn
-    {0xA9810000u, 46u}, // bmk -> Latn
-    {0xC1810000u, 46u}, // bmq -> Latn
-    {0xD1810000u, 46u}, // bmu -> Latn
+    {0xB5210000u, 45u}, // bjn -> Latn
+    {0xB9210000u, 45u}, // bjo -> Latn
+    {0xC5210000u, 45u}, // bjr -> Latn
+    {0xCD210000u, 45u}, // bjt -> Latn
+    {0xE5210000u, 45u}, // bjz -> Latn
+    {0x89410000u, 45u}, // bkc -> Latn
+    {0xB1410000u, 45u}, // bkm -> Latn
+    {0xC1410000u, 45u}, // bkq -> Latn
+    {0xD1410000u, 45u}, // bku -> Latn
+    {0xD5410000u, 45u}, // bkv -> Latn
+    {0x99610000u, 45u}, // blg -> Latn
+    {0xCD610000u, 88u}, // blt -> Tavt
+    {0x626D0000u, 45u}, // bm -> Latn
+    {0x9D810000u, 45u}, // bmh -> Latn
+    {0xA9810000u, 45u}, // bmk -> Latn
+    {0xC1810000u, 45u}, // bmq -> Latn
+    {0xD1810000u, 45u}, // bmu -> Latn
     {0x626E0000u,  8u}, // bn -> Beng
-    {0x99A10000u, 46u}, // bng -> Latn
-    {0xB1A10000u, 46u}, // bnm -> Latn
-    {0xBDA10000u, 46u}, // bnp -> Latn
-    {0x626F0000u, 92u}, // bo -> Tibt
-    {0xA5C10000u, 46u}, // boj -> Latn
-    {0xB1C10000u, 46u}, // bom -> Latn
-    {0xB5C10000u, 46u}, // bon -> Latn
+    {0x99A10000u, 45u}, // bng -> Latn
+    {0xB1A10000u, 45u}, // bnm -> Latn
+    {0xBDA10000u, 45u}, // bnp -> Latn
+    {0x626F0000u, 93u}, // bo -> Tibt
+    {0xA5C10000u, 45u}, // boj -> Latn
+    {0xB1C10000u, 45u}, // bom -> Latn
+    {0xB5C10000u, 45u}, // bon -> Latn
     {0xE1E10000u,  8u}, // bpy -> Beng
-    {0x8A010000u, 46u}, // bqc -> Latn
+    {0x8A010000u, 45u}, // bqc -> Latn
     {0xA2010000u,  2u}, // bqi -> Arab
-    {0xBE010000u, 46u}, // bqp -> Latn
-    {0xD6010000u, 46u}, // bqv -> Latn
-    {0x62720000u, 46u}, // br -> Latn
+    {0xBE010000u, 45u}, // bqp -> Latn
+    {0xD6010000u, 45u}, // bqv -> Latn
+    {0x62720000u, 45u}, // br -> Latn
     {0x82210000u, 19u}, // bra -> Deva
     {0x9E210000u,  2u}, // brh -> Arab
     {0xDE210000u, 19u}, // brx -> Deva
-    {0xE6210000u, 46u}, // brz -> Latn
-    {0x62730000u, 46u}, // bs -> Latn
-    {0xA6410000u, 46u}, // bsj -> Latn
+    {0xE6210000u, 45u}, // brz -> Latn
+    {0x62730000u, 45u}, // bs -> Latn
+    {0xA6410000u, 45u}, // bsj -> Latn
     {0xC2410000u,  7u}, // bsq -> Bass
-    {0xCA410000u, 46u}, // bss -> Latn
+    {0xCA410000u, 45u}, // bss -> Latn
     {0xCE410000u, 21u}, // bst -> Ethi
-    {0xBA610000u, 46u}, // bto -> Latn
-    {0xCE610000u, 46u}, // btt -> Latn
+    {0xBA610000u, 45u}, // bto -> Latn
+    {0xCE610000u, 45u}, // btt -> Latn
     {0xD6610000u, 19u}, // btv -> Deva
     {0x82810000u, 18u}, // bua -> Cyrl
-    {0x8A810000u, 46u}, // buc -> Latn
-    {0x8E810000u, 46u}, // bud -> Latn
-    {0x9A810000u, 46u}, // bug -> Latn
-    {0xAA810000u, 46u}, // buk -> Latn
-    {0xB2810000u, 46u}, // bum -> Latn
-    {0xBA810000u, 46u}, // buo -> Latn
-    {0xCA810000u, 46u}, // bus -> Latn
-    {0xD2810000u, 46u}, // buu -> Latn
-    {0x86A10000u, 46u}, // bvb -> Latn
-    {0x8EC10000u, 46u}, // bwd -> Latn
-    {0xC6C10000u, 46u}, // bwr -> Latn
-    {0x9EE10000u, 46u}, // bxh -> Latn
-    {0x93010000u, 46u}, // bye -> Latn
+    {0x8A810000u, 45u}, // buc -> Latn
+    {0x8E810000u, 45u}, // bud -> Latn
+    {0x9A810000u, 45u}, // bug -> Latn
+    {0xAA810000u, 45u}, // buk -> Latn
+    {0xB2810000u, 45u}, // bum -> Latn
+    {0xBA810000u, 45u}, // buo -> Latn
+    {0xCA810000u, 45u}, // bus -> Latn
+    {0xD2810000u, 45u}, // buu -> Latn
+    {0x86A10000u, 45u}, // bvb -> Latn
+    {0x8EC10000u, 45u}, // bwd -> Latn
+    {0xC6C10000u, 45u}, // bwr -> Latn
+    {0x9EE10000u, 45u}, // bxh -> Latn
+    {0x93010000u, 45u}, // bye -> Latn
     {0xB7010000u, 21u}, // byn -> Ethi
-    {0xC7010000u, 46u}, // byr -> Latn
-    {0xCB010000u, 46u}, // bys -> Latn
-    {0xD7010000u, 46u}, // byv -> Latn
-    {0xDF010000u, 46u}, // byx -> Latn
-    {0x83210000u, 46u}, // bza -> Latn
-    {0x93210000u, 46u}, // bze -> Latn
-    {0x97210000u, 46u}, // bzf -> Latn
-    {0x9F210000u, 46u}, // bzh -> Latn
-    {0xDB210000u, 46u}, // bzw -> Latn
-    {0x63610000u, 46u}, // ca -> Latn
-    {0x8C020000u, 46u}, // cad -> Latn
-    {0xB4020000u, 46u}, // can -> Latn
-    {0xA4220000u, 46u}, // cbj -> Latn
-    {0x9C420000u, 46u}, // cch -> Latn
+    {0xC7010000u, 45u}, // byr -> Latn
+    {0xCB010000u, 45u}, // bys -> Latn
+    {0xD7010000u, 45u}, // byv -> Latn
+    {0xDF010000u, 45u}, // byx -> Latn
+    {0x83210000u, 45u}, // bza -> Latn
+    {0x93210000u, 45u}, // bze -> Latn
+    {0x97210000u, 45u}, // bzf -> Latn
+    {0x9F210000u, 45u}, // bzh -> Latn
+    {0xDB210000u, 45u}, // bzw -> Latn
+    {0x63610000u, 45u}, // ca -> Latn
+    {0x8C020000u, 45u}, // cad -> Latn
+    {0xB4020000u, 45u}, // can -> Latn
+    {0xA4220000u, 45u}, // cbj -> Latn
+    {0x9C420000u, 45u}, // cch -> Latn
     {0xBC420000u, 10u}, // ccp -> Cakm
     {0x63650000u, 18u}, // ce -> Cyrl
-    {0x84820000u, 46u}, // ceb -> Latn
-    {0x80A20000u, 46u}, // cfa -> Latn
-    {0x98C20000u, 46u}, // cgg -> Latn
-    {0x63680000u, 46u}, // ch -> Latn
-    {0xA8E20000u, 46u}, // chk -> Latn
+    {0x84820000u, 45u}, // ceb -> Latn
+    {0x80A20000u, 45u}, // cfa -> Latn
+    {0x98C20000u, 45u}, // cgg -> Latn
+    {0x63680000u, 45u}, // ch -> Latn
+    {0xA8E20000u, 45u}, // chk -> Latn
     {0xB0E20000u, 18u}, // chm -> Cyrl
-    {0xB8E20000u, 46u}, // cho -> Latn
-    {0xBCE20000u, 46u}, // chp -> Latn
+    {0xB8E20000u, 45u}, // cho -> Latn
+    {0xBCE20000u, 45u}, // chp -> Latn
     {0xC4E20000u, 14u}, // chr -> Cher
-    {0x89020000u, 46u}, // cic -> Latn
+    {0x89020000u, 45u}, // cic -> Latn
     {0x81220000u,  2u}, // cja -> Arab
     {0xB1220000u, 13u}, // cjm -> Cham
-    {0xD5220000u, 46u}, // cjv -> Latn
+    {0xD5220000u, 45u}, // cjv -> Latn
     {0x85420000u,  2u}, // ckb -> Arab
-    {0xAD420000u, 46u}, // ckl -> Latn
-    {0xB9420000u, 46u}, // cko -> Latn
-    {0xE1420000u, 46u}, // cky -> Latn
-    {0x81620000u, 46u}, // cla -> Latn
-    {0x91820000u, 46u}, // cme -> Latn
-    {0x99820000u, 81u}, // cmg -> Soyo
-    {0x636F0000u, 46u}, // co -> Latn
+    {0xAD420000u, 45u}, // ckl -> Latn
+    {0xB9420000u, 45u}, // cko -> Latn
+    {0xE1420000u, 45u}, // cky -> Latn
+    {0x81620000u, 45u}, // cla -> Latn
+    {0x91820000u, 45u}, // cme -> Latn
+    {0x99820000u, 82u}, // cmg -> Soyo
+    {0x636F0000u, 45u}, // co -> Latn
     {0xBDC20000u, 16u}, // cop -> Copt
-    {0xC9E20000u, 46u}, // cps -> Latn
+    {0xC9E20000u, 45u}, // cps -> Latn
     {0x63720000u, 11u}, // cr -> Cans
     {0x9E220000u, 18u}, // crh -> Cyrl
     {0xA6220000u, 11u}, // crj -> Cans
     {0xAA220000u, 11u}, // crk -> Cans
     {0xAE220000u, 11u}, // crl -> Cans
     {0xB2220000u, 11u}, // crm -> Cans
-    {0xCA220000u, 46u}, // crs -> Latn
-    {0x63730000u, 46u}, // cs -> Latn
-    {0x86420000u, 46u}, // csb -> Latn
+    {0xCA220000u, 45u}, // crs -> Latn
+    {0x63730000u, 45u}, // cs -> Latn
+    {0x86420000u, 45u}, // csb -> Latn
     {0xDA420000u, 11u}, // csw -> Cans
     {0x8E620000u, 68u}, // ctd -> Pauc
     {0x63750000u, 18u}, // cu -> Cyrl
     {0x63760000u, 18u}, // cv -> Cyrl
-    {0x63790000u, 46u}, // cy -> Latn
-    {0x64610000u, 46u}, // da -> Latn
-    {0x8C030000u, 46u}, // dad -> Latn
-    {0x94030000u, 46u}, // daf -> Latn
-    {0x98030000u, 46u}, // dag -> Latn
-    {0x9C030000u, 46u}, // dah -> Latn
-    {0xA8030000u, 46u}, // dak -> Latn
+    {0x63790000u, 45u}, // cy -> Latn
+    {0x64610000u, 45u}, // da -> Latn
+    {0x8C030000u, 45u}, // dad -> Latn
+    {0x94030000u, 45u}, // daf -> Latn
+    {0x98030000u, 45u}, // dag -> Latn
+    {0x9C030000u, 45u}, // dah -> Latn
+    {0xA8030000u, 45u}, // dak -> Latn
     {0xC4030000u, 18u}, // dar -> Cyrl
-    {0xD4030000u, 46u}, // dav -> Latn
-    {0x8C230000u, 46u}, // dbd -> Latn
-    {0xC0230000u, 46u}, // dbq -> Latn
+    {0xD4030000u, 45u}, // dav -> Latn
+    {0x8C230000u, 45u}, // dbd -> Latn
+    {0xC0230000u, 45u}, // dbq -> Latn
     {0x88430000u,  2u}, // dcc -> Arab
-    {0xB4630000u, 46u}, // ddn -> Latn
-    {0x64650000u, 46u}, // de -> Latn
-    {0x8C830000u, 46u}, // ded -> Latn
-    {0xB4830000u, 46u}, // den -> Latn
-    {0x80C30000u, 46u}, // dga -> Latn
-    {0x9CC30000u, 46u}, // dgh -> Latn
-    {0xA0C30000u, 46u}, // dgi -> Latn
+    {0xB4630000u, 45u}, // ddn -> Latn
+    {0x64650000u, 45u}, // de -> Latn
+    {0x8C830000u, 45u}, // ded -> Latn
+    {0xB4830000u, 45u}, // den -> Latn
+    {0x80C30000u, 45u}, // dga -> Latn
+    {0x9CC30000u, 45u}, // dgh -> Latn
+    {0xA0C30000u, 45u}, // dgi -> Latn
     {0xACC30000u,  2u}, // dgl -> Arab
-    {0xC4C30000u, 46u}, // dgr -> Latn
-    {0xE4C30000u, 46u}, // dgz -> Latn
-    {0x81030000u, 46u}, // dia -> Latn
-    {0x91230000u, 46u}, // dje -> Latn
-    {0x95830000u, 54u}, // dmf -> Medf
-    {0xA5A30000u, 46u}, // dnj -> Latn
-    {0x85C30000u, 46u}, // dob -> Latn
+    {0xC4C30000u, 45u}, // dgr -> Latn
+    {0xE4C30000u, 45u}, // dgz -> Latn
+    {0x81030000u, 45u}, // dia -> Latn
+    {0x91230000u, 45u}, // dje -> Latn
+    {0x95830000u, 53u}, // dmf -> Medf
+    {0xA5A30000u, 45u}, // dnj -> Latn
+    {0x85C30000u, 45u}, // dob -> Latn
     {0xA1C30000u, 19u}, // doi -> Deva
-    {0xBDC30000u, 46u}, // dop -> Latn
-    {0xD9C30000u, 46u}, // dow -> Latn
-    {0x9E230000u, 57u}, // drh -> Mong
-    {0xA2230000u, 46u}, // dri -> Latn
+    {0xBDC30000u, 45u}, // dop -> Latn
+    {0xD9C30000u, 45u}, // dow -> Latn
+    {0x9E230000u, 56u}, // drh -> Mong
+    {0xA2230000u, 45u}, // dri -> Latn
     {0xCA230000u, 21u}, // drs -> Ethi
-    {0x86430000u, 46u}, // dsb -> Latn
-    {0xB2630000u, 46u}, // dtm -> Latn
-    {0xBE630000u, 46u}, // dtp -> Latn
-    {0xCA630000u, 46u}, // dts -> Latn
+    {0x86430000u, 45u}, // dsb -> Latn
+    {0xB2630000u, 45u}, // dtm -> Latn
+    {0xBE630000u, 45u}, // dtp -> Latn
+    {0xCA630000u, 45u}, // dts -> Latn
     {0xE2630000u, 19u}, // dty -> Deva
-    {0x82830000u, 46u}, // dua -> Latn
-    {0x8A830000u, 46u}, // duc -> Latn
-    {0x8E830000u, 46u}, // dud -> Latn
-    {0x9A830000u, 46u}, // dug -> Latn
-    {0x64760000u, 90u}, // dv -> Thaa
-    {0x82A30000u, 46u}, // dva -> Latn
-    {0xDAC30000u, 46u}, // dww -> Latn
-    {0xBB030000u, 46u}, // dyo -> Latn
-    {0xD3030000u, 46u}, // dyu -> Latn
-    {0x647A0000u, 92u}, // dz -> Tibt
-    {0x9B230000u, 46u}, // dzg -> Latn
-    {0xD0240000u, 46u}, // ebu -> Latn
-    {0x65650000u, 46u}, // ee -> Latn
-    {0xA0A40000u, 46u}, // efi -> Latn
-    {0xACC40000u, 46u}, // egl -> Latn
+    {0x82830000u, 45u}, // dua -> Latn
+    {0x8A830000u, 45u}, // duc -> Latn
+    {0x8E830000u, 45u}, // dud -> Latn
+    {0x9A830000u, 45u}, // dug -> Latn
+    {0x64760000u, 91u}, // dv -> Thaa
+    {0x82A30000u, 45u}, // dva -> Latn
+    {0xDAC30000u, 45u}, // dww -> Latn
+    {0xBB030000u, 45u}, // dyo -> Latn
+    {0xD3030000u, 45u}, // dyu -> Latn
+    {0x647A0000u, 93u}, // dz -> Tibt
+    {0x9B230000u, 45u}, // dzg -> Latn
+    {0xD0240000u, 45u}, // ebu -> Latn
+    {0x65650000u, 45u}, // ee -> Latn
+    {0xA0A40000u, 45u}, // efi -> Latn
+    {0xACC40000u, 45u}, // egl -> Latn
     {0xE0C40000u, 20u}, // egy -> Egyp
-    {0x81440000u, 46u}, // eka -> Latn
-    {0xE1440000u, 37u}, // eky -> Kali
+    {0x81440000u, 45u}, // eka -> Latn
+    {0xE1440000u, 36u}, // eky -> Kali
     {0x656C0000u, 26u}, // el -> Grek
-    {0x81840000u, 46u}, // ema -> Latn
-    {0xA1840000u, 46u}, // emi -> Latn
-    {0x656E0000u, 46u}, // en -> Latn
-    {0x656E5841u, 99u}, // en-XA -> ~~~A
-    {0xB5A40000u, 46u}, // enn -> Latn
-    {0xC1A40000u, 46u}, // enq -> Latn
-    {0x656F0000u, 46u}, // eo -> Latn
-    {0xA2240000u, 46u}, // eri -> Latn
-    {0x65730000u, 46u}, // es -> Latn
+    {0x81840000u, 45u}, // ema -> Latn
+    {0xA1840000u, 45u}, // emi -> Latn
+    {0x656E0000u, 45u}, // en -> Latn
+    {0x656E5841u, 102u}, // en-XA -> ~~~A
+    {0xB5A40000u, 45u}, // enn -> Latn
+    {0xC1A40000u, 45u}, // enq -> Latn
+    {0x656F0000u, 45u}, // eo -> Latn
+    {0xA2240000u, 45u}, // eri -> Latn
+    {0x65730000u, 45u}, // es -> Latn
     {0x9A440000u, 24u}, // esg -> Gonm
-    {0xD2440000u, 46u}, // esu -> Latn
-    {0x65740000u, 46u}, // et -> Latn
-    {0xC6640000u, 46u}, // etr -> Latn
-    {0xCE640000u, 35u}, // ett -> Ital
-    {0xD2640000u, 46u}, // etu -> Latn
-    {0xDE640000u, 46u}, // etx -> Latn
-    {0x65750000u, 46u}, // eu -> Latn
-    {0xBAC40000u, 46u}, // ewo -> Latn
-    {0xCEE40000u, 46u}, // ext -> Latn
-    {0x83240000u, 46u}, // eza -> Latn
+    {0xD2440000u, 45u}, // esu -> Latn
+    {0x65740000u, 45u}, // et -> Latn
+    {0xC6640000u, 45u}, // etr -> Latn
+    {0xCE640000u, 34u}, // ett -> Ital
+    {0xD2640000u, 45u}, // etu -> Latn
+    {0xDE640000u, 45u}, // etx -> Latn
+    {0x65750000u, 45u}, // eu -> Latn
+    {0xBAC40000u, 45u}, // ewo -> Latn
+    {0xCEE40000u, 45u}, // ext -> Latn
+    {0x83240000u, 45u}, // eza -> Latn
     {0x66610000u,  2u}, // fa -> Arab
-    {0x80050000u, 46u}, // faa -> Latn
-    {0x84050000u, 46u}, // fab -> Latn
-    {0x98050000u, 46u}, // fag -> Latn
-    {0xA0050000u, 46u}, // fai -> Latn
-    {0xB4050000u, 46u}, // fan -> Latn
-    {0x66660000u, 46u}, // ff -> Latn
-    {0xA0A50000u, 46u}, // ffi -> Latn
-    {0xB0A50000u, 46u}, // ffm -> Latn
-    {0x66690000u, 46u}, // fi -> Latn
+    {0x80050000u, 45u}, // faa -> Latn
+    {0x84050000u, 45u}, // fab -> Latn
+    {0x98050000u, 45u}, // fag -> Latn
+    {0xA0050000u, 45u}, // fai -> Latn
+    {0xB4050000u, 45u}, // fan -> Latn
+    {0x66660000u, 45u}, // ff -> Latn
+    {0xA0A50000u, 45u}, // ffi -> Latn
+    {0xB0A50000u, 45u}, // ffm -> Latn
+    {0x66690000u, 45u}, // fi -> Latn
     {0x81050000u,  2u}, // fia -> Arab
-    {0xAD050000u, 46u}, // fil -> Latn
-    {0xCD050000u, 46u}, // fit -> Latn
-    {0x666A0000u, 46u}, // fj -> Latn
-    {0xC5650000u, 46u}, // flr -> Latn
-    {0xBD850000u, 46u}, // fmp -> Latn
-    {0x666F0000u, 46u}, // fo -> Latn
-    {0x8DC50000u, 46u}, // fod -> Latn
-    {0xB5C50000u, 46u}, // fon -> Latn
-    {0xC5C50000u, 46u}, // for -> Latn
-    {0x91E50000u, 46u}, // fpe -> Latn
-    {0xCA050000u, 46u}, // fqs -> Latn
-    {0x66720000u, 46u}, // fr -> Latn
-    {0x8A250000u, 46u}, // frc -> Latn
-    {0xBE250000u, 46u}, // frp -> Latn
-    {0xC6250000u, 46u}, // frr -> Latn
-    {0xCA250000u, 46u}, // frs -> Latn
+    {0xAD050000u, 45u}, // fil -> Latn
+    {0xCD050000u, 45u}, // fit -> Latn
+    {0x666A0000u, 45u}, // fj -> Latn
+    {0xC5650000u, 45u}, // flr -> Latn
+    {0xBD850000u, 45u}, // fmp -> Latn
+    {0x666F0000u, 45u}, // fo -> Latn
+    {0x8DC50000u, 45u}, // fod -> Latn
+    {0xB5C50000u, 45u}, // fon -> Latn
+    {0xC5C50000u, 45u}, // for -> Latn
+    {0x91E50000u, 45u}, // fpe -> Latn
+    {0xCA050000u, 45u}, // fqs -> Latn
+    {0x66720000u, 45u}, // fr -> Latn
+    {0x8A250000u, 45u}, // frc -> Latn
+    {0xBE250000u, 45u}, // frp -> Latn
+    {0xC6250000u, 45u}, // frr -> Latn
+    {0xCA250000u, 45u}, // frs -> Latn
     {0x86850000u,  2u}, // fub -> Arab
-    {0x8E850000u, 46u}, // fud -> Latn
-    {0x92850000u, 46u}, // fue -> Latn
-    {0x96850000u, 46u}, // fuf -> Latn
-    {0x9E850000u, 46u}, // fuh -> Latn
-    {0xC2850000u, 46u}, // fuq -> Latn
-    {0xC6850000u, 46u}, // fur -> Latn
-    {0xD6850000u, 46u}, // fuv -> Latn
-    {0xE2850000u, 46u}, // fuy -> Latn
-    {0xC6A50000u, 46u}, // fvr -> Latn
-    {0x66790000u, 46u}, // fy -> Latn
-    {0x67610000u, 46u}, // ga -> Latn
-    {0x80060000u, 46u}, // gaa -> Latn
-    {0x94060000u, 46u}, // gaf -> Latn
-    {0x98060000u, 46u}, // gag -> Latn
-    {0x9C060000u, 46u}, // gah -> Latn
-    {0xA4060000u, 46u}, // gaj -> Latn
-    {0xB0060000u, 46u}, // gam -> Latn
+    {0x8E850000u, 45u}, // fud -> Latn
+    {0x92850000u, 45u}, // fue -> Latn
+    {0x96850000u, 45u}, // fuf -> Latn
+    {0x9E850000u, 45u}, // fuh -> Latn
+    {0xC2850000u, 45u}, // fuq -> Latn
+    {0xC6850000u, 45u}, // fur -> Latn
+    {0xD6850000u, 45u}, // fuv -> Latn
+    {0xE2850000u, 45u}, // fuy -> Latn
+    {0xC6A50000u, 45u}, // fvr -> Latn
+    {0x66790000u, 45u}, // fy -> Latn
+    {0x67610000u, 45u}, // ga -> Latn
+    {0x80060000u, 45u}, // gaa -> Latn
+    {0x94060000u, 45u}, // gaf -> Latn
+    {0x98060000u, 45u}, // gag -> Latn
+    {0x9C060000u, 45u}, // gah -> Latn
+    {0xA4060000u, 45u}, // gaj -> Latn
+    {0xB0060000u, 45u}, // gam -> Latn
     {0xB4060000u, 29u}, // gan -> Hans
-    {0xD8060000u, 46u}, // gaw -> Latn
-    {0xE0060000u, 46u}, // gay -> Latn
-    {0x80260000u, 46u}, // gba -> Latn
-    {0x94260000u, 46u}, // gbf -> Latn
+    {0xD8060000u, 45u}, // gaw -> Latn
+    {0xE0060000u, 45u}, // gay -> Latn
+    {0x80260000u, 45u}, // gba -> Latn
+    {0x94260000u, 45u}, // gbf -> Latn
     {0xB0260000u, 19u}, // gbm -> Deva
-    {0xE0260000u, 46u}, // gby -> Latn
+    {0xE0260000u, 45u}, // gby -> Latn
     {0xE4260000u,  2u}, // gbz -> Arab
-    {0xC4460000u, 46u}, // gcr -> Latn
-    {0x67640000u, 46u}, // gd -> Latn
-    {0x90660000u, 46u}, // gde -> Latn
-    {0xB4660000u, 46u}, // gdn -> Latn
-    {0xC4660000u, 46u}, // gdr -> Latn
-    {0x84860000u, 46u}, // geb -> Latn
-    {0xA4860000u, 46u}, // gej -> Latn
-    {0xAC860000u, 46u}, // gel -> Latn
+    {0xC4460000u, 45u}, // gcr -> Latn
+    {0x67640000u, 45u}, // gd -> Latn
+    {0x90660000u, 45u}, // gde -> Latn
+    {0xB4660000u, 45u}, // gdn -> Latn
+    {0xC4660000u, 45u}, // gdr -> Latn
+    {0x84860000u, 45u}, // geb -> Latn
+    {0xA4860000u, 45u}, // gej -> Latn
+    {0xAC860000u, 45u}, // gel -> Latn
     {0xE4860000u, 21u}, // gez -> Ethi
-    {0xA8A60000u, 46u}, // gfk -> Latn
+    {0xA8A60000u, 45u}, // gfk -> Latn
     {0xB4C60000u, 19u}, // ggn -> Deva
-    {0xC8E60000u, 46u}, // ghs -> Latn
-    {0xAD060000u, 46u}, // gil -> Latn
-    {0xB1060000u, 46u}, // gim -> Latn
+    {0xC8E60000u, 45u}, // ghs -> Latn
+    {0xAD060000u, 45u}, // gil -> Latn
+    {0xB1060000u, 45u}, // gim -> Latn
     {0xA9260000u,  2u}, // gjk -> Arab
-    {0xB5260000u, 46u}, // gjn -> Latn
+    {0xB5260000u, 45u}, // gjn -> Latn
     {0xD1260000u,  2u}, // gju -> Arab
-    {0xB5460000u, 46u}, // gkn -> Latn
-    {0xBD460000u, 46u}, // gkp -> Latn
-    {0x676C0000u, 46u}, // gl -> Latn
+    {0xB5460000u, 45u}, // gkn -> Latn
+    {0xBD460000u, 45u}, // gkp -> Latn
+    {0x676C0000u, 45u}, // gl -> Latn
     {0xA9660000u,  2u}, // glk -> Arab
-    {0xB1860000u, 46u}, // gmm -> Latn
+    {0xB1860000u, 45u}, // gmm -> Latn
     {0xD5860000u, 21u}, // gmv -> Ethi
-    {0x676E0000u, 46u}, // gn -> Latn
-    {0x8DA60000u, 46u}, // gnd -> Latn
-    {0x99A60000u, 46u}, // gng -> Latn
-    {0x8DC60000u, 46u}, // god -> Latn
+    {0x676E0000u, 45u}, // gn -> Latn
+    {0x8DA60000u, 45u}, // gnd -> Latn
+    {0x99A60000u, 45u}, // gng -> Latn
+    {0x8DC60000u, 45u}, // god -> Latn
     {0x95C60000u, 21u}, // gof -> Ethi
-    {0xA1C60000u, 46u}, // goi -> Latn
+    {0xA1C60000u, 45u}, // goi -> Latn
     {0xB1C60000u, 19u}, // gom -> Deva
-    {0xB5C60000u, 88u}, // gon -> Telu
-    {0xC5C60000u, 46u}, // gor -> Latn
-    {0xC9C60000u, 46u}, // gos -> Latn
+    {0xB5C60000u, 89u}, // gon -> Telu
+    {0xC5C60000u, 45u}, // gor -> Latn
+    {0xC9C60000u, 45u}, // gos -> Latn
     {0xCDC60000u, 25u}, // got -> Goth
-    {0x86260000u, 46u}, // grb -> Latn
+    {0x86260000u, 45u}, // grb -> Latn
     {0x8A260000u, 17u}, // grc -> Cprt
     {0xCE260000u,  8u}, // grt -> Beng
-    {0xDA260000u, 46u}, // grw -> Latn
-    {0xDA460000u, 46u}, // gsw -> Latn
+    {0xDA260000u, 45u}, // grw -> Latn
+    {0xDA460000u, 45u}, // gsw -> Latn
     {0x67750000u, 27u}, // gu -> Gujr
-    {0x86860000u, 46u}, // gub -> Latn
-    {0x8A860000u, 46u}, // guc -> Latn
-    {0x8E860000u, 46u}, // gud -> Latn
-    {0xC6860000u, 46u}, // gur -> Latn
-    {0xDA860000u, 46u}, // guw -> Latn
-    {0xDE860000u, 46u}, // gux -> Latn
-    {0xE6860000u, 46u}, // guz -> Latn
-    {0x67760000u, 46u}, // gv -> Latn
-    {0x96A60000u, 46u}, // gvf -> Latn
+    {0x86860000u, 45u}, // gub -> Latn
+    {0x8A860000u, 45u}, // guc -> Latn
+    {0x8E860000u, 45u}, // gud -> Latn
+    {0xC6860000u, 45u}, // gur -> Latn
+    {0xDA860000u, 45u}, // guw -> Latn
+    {0xDE860000u, 45u}, // gux -> Latn
+    {0xE6860000u, 45u}, // guz -> Latn
+    {0x67760000u, 45u}, // gv -> Latn
+    {0x96A60000u, 45u}, // gvf -> Latn
     {0xC6A60000u, 19u}, // gvr -> Deva
-    {0xCAA60000u, 46u}, // gvs -> Latn
+    {0xCAA60000u, 45u}, // gvs -> Latn
     {0x8AC60000u,  2u}, // gwc -> Arab
-    {0xA2C60000u, 46u}, // gwi -> Latn
+    {0xA2C60000u, 45u}, // gwi -> Latn
     {0xCEC60000u,  2u}, // gwt -> Arab
-    {0xA3060000u, 46u}, // gyi -> Latn
-    {0x68610000u, 46u}, // ha -> Latn
+    {0xA3060000u, 45u}, // gyi -> Latn
+    {0x68610000u, 45u}, // ha -> Latn
     {0x6861434Du,  2u}, // ha-CM -> Arab
     {0x68615344u,  2u}, // ha-SD -> Arab
-    {0x98070000u, 46u}, // hag -> Latn
+    {0x98070000u, 45u}, // hag -> Latn
     {0xA8070000u, 29u}, // hak -> Hans
-    {0xB0070000u, 46u}, // ham -> Latn
-    {0xD8070000u, 46u}, // haw -> Latn
+    {0xB0070000u, 45u}, // ham -> Latn
+    {0xD8070000u, 45u}, // haw -> Latn
     {0xE4070000u,  2u}, // haz -> Arab
-    {0x84270000u, 46u}, // hbb -> Latn
+    {0x84270000u, 45u}, // hbb -> Latn
     {0xE0670000u, 21u}, // hdy -> Ethi
     {0x68650000u, 31u}, // he -> Hebr
-    {0xE0E70000u, 46u}, // hhy -> Latn
+    {0xE0E70000u, 45u}, // hhy -> Latn
     {0x68690000u, 19u}, // hi -> Deva
-    {0x81070000u, 46u}, // hia -> Latn
-    {0x95070000u, 46u}, // hif -> Latn
-    {0x99070000u, 46u}, // hig -> Latn
-    {0x9D070000u, 46u}, // hih -> Latn
-    {0xAD070000u, 46u}, // hil -> Latn
-    {0x81670000u, 46u}, // hla -> Latn
+    {0x81070000u, 45u}, // hia -> Latn
+    {0x95070000u, 45u}, // hif -> Latn
+    {0x99070000u, 45u}, // hig -> Latn
+    {0x9D070000u, 45u}, // hih -> Latn
+    {0xAD070000u, 45u}, // hil -> Latn
+    {0x81670000u, 45u}, // hla -> Latn
     {0xD1670000u, 32u}, // hlu -> Hluw
     {0x8D870000u, 71u}, // hmd -> Plrd
-    {0xCD870000u, 46u}, // hmt -> Latn
+    {0xCD870000u, 45u}, // hmt -> Latn
     {0x8DA70000u,  2u}, // hnd -> Arab
     {0x91A70000u, 19u}, // hne -> Deva
-    {0xA5A70000u, 33u}, // hnj -> Hmng
-    {0xB5A70000u, 46u}, // hnn -> Latn
+    {0xA5A70000u, 33u}, // hnj -> Hmnp
+    {0xB5A70000u, 45u}, // hnn -> Latn
     {0xB9A70000u,  2u}, // hno -> Arab
-    {0x686F0000u, 46u}, // ho -> Latn
+    {0x686F0000u, 45u}, // ho -> Latn
     {0x89C70000u, 19u}, // hoc -> Deva
     {0xA5C70000u, 19u}, // hoj -> Deva
-    {0xCDC70000u, 46u}, // hot -> Latn
-    {0x68720000u, 46u}, // hr -> Latn
-    {0x86470000u, 46u}, // hsb -> Latn
+    {0xCDC70000u, 45u}, // hot -> Latn
+    {0x68720000u, 45u}, // hr -> Latn
+    {0x86470000u, 45u}, // hsb -> Latn
     {0xB6470000u, 29u}, // hsn -> Hans
-    {0x68740000u, 46u}, // ht -> Latn
-    {0x68750000u, 46u}, // hu -> Latn
-    {0xA2870000u, 46u}, // hui -> Latn
+    {0x68740000u, 45u}, // ht -> Latn
+    {0x68750000u, 45u}, // hu -> Latn
+    {0xA2870000u, 45u}, // hui -> Latn
     {0x68790000u,  4u}, // hy -> Armn
-    {0x687A0000u, 46u}, // hz -> Latn
-    {0x69610000u, 46u}, // ia -> Latn
-    {0xB4080000u, 46u}, // ian -> Latn
-    {0xC4080000u, 46u}, // iar -> Latn
-    {0x80280000u, 46u}, // iba -> Latn
-    {0x84280000u, 46u}, // ibb -> Latn
-    {0xE0280000u, 46u}, // iby -> Latn
-    {0x80480000u, 46u}, // ica -> Latn
-    {0x9C480000u, 46u}, // ich -> Latn
-    {0x69640000u, 46u}, // id -> Latn
-    {0x8C680000u, 46u}, // idd -> Latn
-    {0xA0680000u, 46u}, // idi -> Latn
-    {0xD0680000u, 46u}, // idu -> Latn
-    {0x90A80000u, 46u}, // ife -> Latn
-    {0x69670000u, 46u}, // ig -> Latn
-    {0x84C80000u, 46u}, // igb -> Latn
-    {0x90C80000u, 46u}, // ige -> Latn
-    {0x69690000u, 98u}, // ii -> Yiii
-    {0xA5280000u, 46u}, // ijj -> Latn
-    {0x696B0000u, 46u}, // ik -> Latn
-    {0xA9480000u, 46u}, // ikk -> Latn
-    {0xCD480000u, 46u}, // ikt -> Latn
-    {0xD9480000u, 46u}, // ikw -> Latn
-    {0xDD480000u, 46u}, // ikx -> Latn
-    {0xB9680000u, 46u}, // ilo -> Latn
-    {0xB9880000u, 46u}, // imo -> Latn
-    {0x696E0000u, 46u}, // in -> Latn
+    {0x687A0000u, 45u}, // hz -> Latn
+    {0x69610000u, 45u}, // ia -> Latn
+    {0xB4080000u, 45u}, // ian -> Latn
+    {0xC4080000u, 45u}, // iar -> Latn
+    {0x80280000u, 45u}, // iba -> Latn
+    {0x84280000u, 45u}, // ibb -> Latn
+    {0xE0280000u, 45u}, // iby -> Latn
+    {0x80480000u, 45u}, // ica -> Latn
+    {0x9C480000u, 45u}, // ich -> Latn
+    {0x69640000u, 45u}, // id -> Latn
+    {0x8C680000u, 45u}, // idd -> Latn
+    {0xA0680000u, 45u}, // idi -> Latn
+    {0xD0680000u, 45u}, // idu -> Latn
+    {0x90A80000u, 45u}, // ife -> Latn
+    {0x69670000u, 45u}, // ig -> Latn
+    {0x84C80000u, 45u}, // igb -> Latn
+    {0x90C80000u, 45u}, // ige -> Latn
+    {0x69690000u, 101u}, // ii -> Yiii
+    {0xA5280000u, 45u}, // ijj -> Latn
+    {0x696B0000u, 45u}, // ik -> Latn
+    {0xA9480000u, 45u}, // ikk -> Latn
+    {0xCD480000u, 45u}, // ikt -> Latn
+    {0xD9480000u, 45u}, // ikw -> Latn
+    {0xDD480000u, 45u}, // ikx -> Latn
+    {0xB9680000u, 45u}, // ilo -> Latn
+    {0xB9880000u, 45u}, // imo -> Latn
+    {0x696E0000u, 45u}, // in -> Latn
     {0x9DA80000u, 18u}, // inh -> Cyrl
-    {0x696F0000u, 46u}, // io -> Latn
-    {0xD1C80000u, 46u}, // iou -> Latn
-    {0xA2280000u, 46u}, // iri -> Latn
-    {0x69730000u, 46u}, // is -> Latn
-    {0x69740000u, 46u}, // it -> Latn
+    {0x696F0000u, 45u}, // io -> Latn
+    {0xD1C80000u, 45u}, // iou -> Latn
+    {0xA2280000u, 45u}, // iri -> Latn
+    {0x69730000u, 45u}, // is -> Latn
+    {0x69740000u, 45u}, // it -> Latn
     {0x69750000u, 11u}, // iu -> Cans
     {0x69770000u, 31u}, // iw -> Hebr
-    {0xB2C80000u, 46u}, // iwm -> Latn
-    {0xCAC80000u, 46u}, // iws -> Latn
-    {0x9F280000u, 46u}, // izh -> Latn
-    {0xA3280000u, 46u}, // izi -> Latn
-    {0x6A610000u, 36u}, // ja -> Jpan
-    {0x84090000u, 46u}, // jab -> Latn
-    {0xB0090000u, 46u}, // jam -> Latn
-    {0xC4090000u, 46u}, // jar -> Latn
-    {0xB8290000u, 46u}, // jbo -> Latn
-    {0xD0290000u, 46u}, // jbu -> Latn
-    {0xB4890000u, 46u}, // jen -> Latn
-    {0xA8C90000u, 46u}, // jgk -> Latn
-    {0xB8C90000u, 46u}, // jgo -> Latn
+    {0xB2C80000u, 45u}, // iwm -> Latn
+    {0xCAC80000u, 45u}, // iws -> Latn
+    {0x9F280000u, 45u}, // izh -> Latn
+    {0xA3280000u, 45u}, // izi -> Latn
+    {0x6A610000u, 35u}, // ja -> Jpan
+    {0x84090000u, 45u}, // jab -> Latn
+    {0xB0090000u, 45u}, // jam -> Latn
+    {0xC4090000u, 45u}, // jar -> Latn
+    {0xB8290000u, 45u}, // jbo -> Latn
+    {0xD0290000u, 45u}, // jbu -> Latn
+    {0xB4890000u, 45u}, // jen -> Latn
+    {0xA8C90000u, 45u}, // jgk -> Latn
+    {0xB8C90000u, 45u}, // jgo -> Latn
     {0x6A690000u, 31u}, // ji -> Hebr
-    {0x85090000u, 46u}, // jib -> Latn
-    {0x89890000u, 46u}, // jmc -> Latn
+    {0x85090000u, 45u}, // jib -> Latn
+    {0x89890000u, 45u}, // jmc -> Latn
     {0xAD890000u, 19u}, // jml -> Deva
-    {0x82290000u, 46u}, // jra -> Latn
-    {0xCE890000u, 46u}, // jut -> Latn
-    {0x6A760000u, 46u}, // jv -> Latn
-    {0x6A770000u, 46u}, // jw -> Latn
+    {0x82290000u, 45u}, // jra -> Latn
+    {0xCE890000u, 45u}, // jut -> Latn
+    {0x6A760000u, 45u}, // jv -> Latn
+    {0x6A770000u, 45u}, // jw -> Latn
     {0x6B610000u, 22u}, // ka -> Geor
     {0x800A0000u, 18u}, // kaa -> Cyrl
-    {0x840A0000u, 46u}, // kab -> Latn
-    {0x880A0000u, 46u}, // kac -> Latn
-    {0x8C0A0000u, 46u}, // kad -> Latn
-    {0xA00A0000u, 46u}, // kai -> Latn
-    {0xA40A0000u, 46u}, // kaj -> Latn
-    {0xB00A0000u, 46u}, // kam -> Latn
-    {0xB80A0000u, 46u}, // kao -> Latn
+    {0x840A0000u, 45u}, // kab -> Latn
+    {0x880A0000u, 45u}, // kac -> Latn
+    {0x8C0A0000u, 45u}, // kad -> Latn
+    {0xA00A0000u, 45u}, // kai -> Latn
+    {0xA40A0000u, 45u}, // kaj -> Latn
+    {0xB00A0000u, 45u}, // kam -> Latn
+    {0xB80A0000u, 45u}, // kao -> Latn
     {0x8C2A0000u, 18u}, // kbd -> Cyrl
-    {0xB02A0000u, 46u}, // kbm -> Latn
-    {0xBC2A0000u, 46u}, // kbp -> Latn
-    {0xC02A0000u, 46u}, // kbq -> Latn
-    {0xDC2A0000u, 46u}, // kbx -> Latn
+    {0xB02A0000u, 45u}, // kbm -> Latn
+    {0xBC2A0000u, 45u}, // kbp -> Latn
+    {0xC02A0000u, 45u}, // kbq -> Latn
+    {0xDC2A0000u, 45u}, // kbx -> Latn
     {0xE02A0000u,  2u}, // kby -> Arab
-    {0x984A0000u, 46u}, // kcg -> Latn
-    {0xA84A0000u, 46u}, // kck -> Latn
-    {0xAC4A0000u, 46u}, // kcl -> Latn
-    {0xCC4A0000u, 46u}, // kct -> Latn
-    {0x906A0000u, 46u}, // kde -> Latn
-    {0x9C6A0000u,  2u}, // kdh -> Arab
-    {0xAC6A0000u, 46u}, // kdl -> Latn
-    {0xCC6A0000u, 91u}, // kdt -> Thai
-    {0x808A0000u, 46u}, // kea -> Latn
-    {0xB48A0000u, 46u}, // ken -> Latn
-    {0xE48A0000u, 46u}, // kez -> Latn
-    {0xB8AA0000u, 46u}, // kfo -> Latn
+    {0x984A0000u, 45u}, // kcg -> Latn
+    {0xA84A0000u, 45u}, // kck -> Latn
+    {0xAC4A0000u, 45u}, // kcl -> Latn
+    {0xCC4A0000u, 45u}, // kct -> Latn
+    {0x906A0000u, 45u}, // kde -> Latn
+    {0x9C6A0000u, 45u}, // kdh -> Latn
+    {0xAC6A0000u, 45u}, // kdl -> Latn
+    {0xCC6A0000u, 92u}, // kdt -> Thai
+    {0x808A0000u, 45u}, // kea -> Latn
+    {0xB48A0000u, 45u}, // ken -> Latn
+    {0xE48A0000u, 45u}, // kez -> Latn
+    {0xB8AA0000u, 45u}, // kfo -> Latn
     {0xC4AA0000u, 19u}, // kfr -> Deva
     {0xE0AA0000u, 19u}, // kfy -> Deva
-    {0x6B670000u, 46u}, // kg -> Latn
-    {0x90CA0000u, 46u}, // kge -> Latn
-    {0x94CA0000u, 46u}, // kgf -> Latn
-    {0xBCCA0000u, 46u}, // kgp -> Latn
-    {0x80EA0000u, 46u}, // kha -> Latn
-    {0x84EA0000u, 84u}, // khb -> Talu
+    {0x6B670000u, 45u}, // kg -> Latn
+    {0x90CA0000u, 45u}, // kge -> Latn
+    {0x94CA0000u, 45u}, // kgf -> Latn
+    {0xBCCA0000u, 45u}, // kgp -> Latn
+    {0x80EA0000u, 45u}, // kha -> Latn
+    {0x84EA0000u, 85u}, // khb -> Talu
     {0xB4EA0000u, 19u}, // khn -> Deva
-    {0xC0EA0000u, 46u}, // khq -> Latn
-    {0xC8EA0000u, 46u}, // khs -> Latn
-    {0xCCEA0000u, 59u}, // kht -> Mymr
+    {0xC0EA0000u, 45u}, // khq -> Latn
+    {0xC8EA0000u, 45u}, // khs -> Latn
+    {0xCCEA0000u, 58u}, // kht -> Mymr
     {0xD8EA0000u,  2u}, // khw -> Arab
-    {0xE4EA0000u, 46u}, // khz -> Latn
-    {0x6B690000u, 46u}, // ki -> Latn
-    {0xA50A0000u, 46u}, // kij -> Latn
-    {0xD10A0000u, 46u}, // kiu -> Latn
-    {0xD90A0000u, 46u}, // kiw -> Latn
-    {0x6B6A0000u, 46u}, // kj -> Latn
-    {0x8D2A0000u, 46u}, // kjd -> Latn
-    {0x992A0000u, 45u}, // kjg -> Laoo
-    {0xC92A0000u, 46u}, // kjs -> Latn
-    {0xE12A0000u, 46u}, // kjy -> Latn
+    {0xE4EA0000u, 45u}, // khz -> Latn
+    {0x6B690000u, 45u}, // ki -> Latn
+    {0xA50A0000u, 45u}, // kij -> Latn
+    {0xD10A0000u, 45u}, // kiu -> Latn
+    {0xD90A0000u, 45u}, // kiw -> Latn
+    {0x6B6A0000u, 45u}, // kj -> Latn
+    {0x8D2A0000u, 45u}, // kjd -> Latn
+    {0x992A0000u, 44u}, // kjg -> Laoo
+    {0xC92A0000u, 45u}, // kjs -> Latn
+    {0xE12A0000u, 45u}, // kjy -> Latn
     {0x6B6B0000u, 18u}, // kk -> Cyrl
     {0x6B6B4146u,  2u}, // kk-AF -> Arab
     {0x6B6B434Eu,  2u}, // kk-CN -> Arab
     {0x6B6B4952u,  2u}, // kk-IR -> Arab
     {0x6B6B4D4Eu,  2u}, // kk-MN -> Arab
-    {0x894A0000u, 46u}, // kkc -> Latn
-    {0xA54A0000u, 46u}, // kkj -> Latn
-    {0x6B6C0000u, 46u}, // kl -> Latn
-    {0xB56A0000u, 46u}, // kln -> Latn
-    {0xC16A0000u, 46u}, // klq -> Latn
-    {0xCD6A0000u, 46u}, // klt -> Latn
-    {0xDD6A0000u, 46u}, // klx -> Latn
-    {0x6B6D0000u, 40u}, // km -> Khmr
-    {0x858A0000u, 46u}, // kmb -> Latn
-    {0x9D8A0000u, 46u}, // kmh -> Latn
-    {0xB98A0000u, 46u}, // kmo -> Latn
-    {0xC98A0000u, 46u}, // kms -> Latn
-    {0xD18A0000u, 46u}, // kmu -> Latn
-    {0xD98A0000u, 46u}, // kmw -> Latn
-    {0x6B6E0000u, 42u}, // kn -> Knda
-    {0x95AA0000u, 46u}, // knf -> Latn
-    {0xBDAA0000u, 46u}, // knp -> Latn
-    {0x6B6F0000u, 43u}, // ko -> Kore
+    {0x894A0000u, 45u}, // kkc -> Latn
+    {0xA54A0000u, 45u}, // kkj -> Latn
+    {0x6B6C0000u, 45u}, // kl -> Latn
+    {0xB56A0000u, 45u}, // kln -> Latn
+    {0xC16A0000u, 45u}, // klq -> Latn
+    {0xCD6A0000u, 45u}, // klt -> Latn
+    {0xDD6A0000u, 45u}, // klx -> Latn
+    {0x6B6D0000u, 39u}, // km -> Khmr
+    {0x858A0000u, 45u}, // kmb -> Latn
+    {0x9D8A0000u, 45u}, // kmh -> Latn
+    {0xB98A0000u, 45u}, // kmo -> Latn
+    {0xC98A0000u, 45u}, // kms -> Latn
+    {0xD18A0000u, 45u}, // kmu -> Latn
+    {0xD98A0000u, 45u}, // kmw -> Latn
+    {0x6B6E0000u, 41u}, // kn -> Knda
+    {0x95AA0000u, 45u}, // knf -> Latn
+    {0xBDAA0000u, 45u}, // knp -> Latn
+    {0x6B6F0000u, 42u}, // ko -> Kore
     {0xA1CA0000u, 18u}, // koi -> Cyrl
     {0xA9CA0000u, 19u}, // kok -> Deva
-    {0xADCA0000u, 46u}, // kol -> Latn
-    {0xC9CA0000u, 46u}, // kos -> Latn
-    {0xE5CA0000u, 46u}, // koz -> Latn
-    {0x91EA0000u, 46u}, // kpe -> Latn
-    {0x95EA0000u, 46u}, // kpf -> Latn
-    {0xB9EA0000u, 46u}, // kpo -> Latn
-    {0xC5EA0000u, 46u}, // kpr -> Latn
-    {0xDDEA0000u, 46u}, // kpx -> Latn
-    {0x860A0000u, 46u}, // kqb -> Latn
-    {0x960A0000u, 46u}, // kqf -> Latn
-    {0xCA0A0000u, 46u}, // kqs -> Latn
+    {0xADCA0000u, 45u}, // kol -> Latn
+    {0xC9CA0000u, 45u}, // kos -> Latn
+    {0xE5CA0000u, 45u}, // koz -> Latn
+    {0x91EA0000u, 45u}, // kpe -> Latn
+    {0x95EA0000u, 45u}, // kpf -> Latn
+    {0xB9EA0000u, 45u}, // kpo -> Latn
+    {0xC5EA0000u, 45u}, // kpr -> Latn
+    {0xDDEA0000u, 45u}, // kpx -> Latn
+    {0x860A0000u, 45u}, // kqb -> Latn
+    {0x960A0000u, 45u}, // kqf -> Latn
+    {0xCA0A0000u, 45u}, // kqs -> Latn
     {0xE20A0000u, 21u}, // kqy -> Ethi
-    {0x6B720000u, 46u}, // kr -> Latn
+    {0x6B720000u, 45u}, // kr -> Latn
     {0x8A2A0000u, 18u}, // krc -> Cyrl
-    {0xA22A0000u, 46u}, // kri -> Latn
-    {0xA62A0000u, 46u}, // krj -> Latn
-    {0xAE2A0000u, 46u}, // krl -> Latn
-    {0xCA2A0000u, 46u}, // krs -> Latn
+    {0xA22A0000u, 45u}, // kri -> Latn
+    {0xA62A0000u, 45u}, // krj -> Latn
+    {0xAE2A0000u, 45u}, // krl -> Latn
+    {0xCA2A0000u, 45u}, // krs -> Latn
     {0xD22A0000u, 19u}, // kru -> Deva
     {0x6B730000u,  2u}, // ks -> Arab
-    {0x864A0000u, 46u}, // ksb -> Latn
-    {0x8E4A0000u, 46u}, // ksd -> Latn
-    {0x964A0000u, 46u}, // ksf -> Latn
-    {0x9E4A0000u, 46u}, // ksh -> Latn
-    {0xA64A0000u, 46u}, // ksj -> Latn
-    {0xC64A0000u, 46u}, // ksr -> Latn
+    {0x864A0000u, 45u}, // ksb -> Latn
+    {0x8E4A0000u, 45u}, // ksd -> Latn
+    {0x964A0000u, 45u}, // ksf -> Latn
+    {0x9E4A0000u, 45u}, // ksh -> Latn
+    {0xA64A0000u, 45u}, // ksj -> Latn
+    {0xC64A0000u, 45u}, // ksr -> Latn
     {0x866A0000u, 21u}, // ktb -> Ethi
-    {0xB26A0000u, 46u}, // ktm -> Latn
-    {0xBA6A0000u, 46u}, // kto -> Latn
-    {0xC66A0000u, 46u}, // ktr -> Latn
-    {0x6B750000u, 46u}, // ku -> Latn
+    {0xB26A0000u, 45u}, // ktm -> Latn
+    {0xBA6A0000u, 45u}, // kto -> Latn
+    {0xC66A0000u, 45u}, // ktr -> Latn
+    {0x6B750000u, 45u}, // ku -> Latn
     {0x6B754952u,  2u}, // ku-IR -> Arab
     {0x6B754C42u,  2u}, // ku-LB -> Arab
-    {0x868A0000u, 46u}, // kub -> Latn
-    {0x8E8A0000u, 46u}, // kud -> Latn
-    {0x928A0000u, 46u}, // kue -> Latn
-    {0xA68A0000u, 46u}, // kuj -> Latn
+    {0x868A0000u, 45u}, // kub -> Latn
+    {0x8E8A0000u, 45u}, // kud -> Latn
+    {0x928A0000u, 45u}, // kue -> Latn
+    {0xA68A0000u, 45u}, // kuj -> Latn
     {0xB28A0000u, 18u}, // kum -> Cyrl
-    {0xB68A0000u, 46u}, // kun -> Latn
-    {0xBE8A0000u, 46u}, // kup -> Latn
-    {0xCA8A0000u, 46u}, // kus -> Latn
+    {0xB68A0000u, 45u}, // kun -> Latn
+    {0xBE8A0000u, 45u}, // kup -> Latn
+    {0xCA8A0000u, 45u}, // kus -> Latn
     {0x6B760000u, 18u}, // kv -> Cyrl
-    {0x9AAA0000u, 46u}, // kvg -> Latn
-    {0xC6AA0000u, 46u}, // kvr -> Latn
+    {0x9AAA0000u, 45u}, // kvg -> Latn
+    {0xC6AA0000u, 45u}, // kvr -> Latn
     {0xDEAA0000u,  2u}, // kvx -> Arab
-    {0x6B770000u, 46u}, // kw -> Latn
-    {0xA6CA0000u, 46u}, // kwj -> Latn
-    {0xBACA0000u, 46u}, // kwo -> Latn
-    {0xC2CA0000u, 46u}, // kwq -> Latn
-    {0x82EA0000u, 46u}, // kxa -> Latn
+    {0x6B770000u, 45u}, // kw -> Latn
+    {0xA6CA0000u, 45u}, // kwj -> Latn
+    {0xBACA0000u, 45u}, // kwo -> Latn
+    {0xC2CA0000u, 45u}, // kwq -> Latn
+    {0x82EA0000u, 45u}, // kxa -> Latn
     {0x8AEA0000u, 21u}, // kxc -> Ethi
-    {0x92EA0000u, 46u}, // kxe -> Latn
+    {0x92EA0000u, 45u}, // kxe -> Latn
     {0xAEEA0000u, 19u}, // kxl -> Deva
-    {0xB2EA0000u, 91u}, // kxm -> Thai
+    {0xB2EA0000u, 92u}, // kxm -> Thai
     {0xBEEA0000u,  2u}, // kxp -> Arab
-    {0xDAEA0000u, 46u}, // kxw -> Latn
-    {0xE6EA0000u, 46u}, // kxz -> Latn
+    {0xDAEA0000u, 45u}, // kxw -> Latn
+    {0xE6EA0000u, 45u}, // kxz -> Latn
     {0x6B790000u, 18u}, // ky -> Cyrl
     {0x6B79434Eu,  2u}, // ky-CN -> Arab
-    {0x6B795452u, 46u}, // ky-TR -> Latn
-    {0x930A0000u, 46u}, // kye -> Latn
-    {0xDF0A0000u, 46u}, // kyx -> Latn
+    {0x6B795452u, 45u}, // ky-TR -> Latn
+    {0x930A0000u, 45u}, // kye -> Latn
+    {0xDF0A0000u, 45u}, // kyx -> Latn
     {0x9F2A0000u,  2u}, // kzh -> Arab
-    {0xA72A0000u, 46u}, // kzj -> Latn
-    {0xC72A0000u, 46u}, // kzr -> Latn
-    {0xCF2A0000u, 46u}, // kzt -> Latn
-    {0x6C610000u, 46u}, // la -> Latn
-    {0x840B0000u, 48u}, // lab -> Lina
+    {0xA72A0000u, 45u}, // kzj -> Latn
+    {0xC72A0000u, 45u}, // kzr -> Latn
+    {0xCF2A0000u, 45u}, // kzt -> Latn
+    {0x6C610000u, 45u}, // la -> Latn
+    {0x840B0000u, 47u}, // lab -> Lina
     {0x8C0B0000u, 31u}, // lad -> Hebr
-    {0x980B0000u, 46u}, // lag -> Latn
+    {0x980B0000u, 45u}, // lag -> Latn
     {0x9C0B0000u,  2u}, // lah -> Arab
-    {0xA40B0000u, 46u}, // laj -> Latn
-    {0xC80B0000u, 46u}, // las -> Latn
-    {0x6C620000u, 46u}, // lb -> Latn
+    {0xA40B0000u, 45u}, // laj -> Latn
+    {0xC80B0000u, 45u}, // las -> Latn
+    {0x6C620000u, 45u}, // lb -> Latn
     {0x902B0000u, 18u}, // lbe -> Cyrl
-    {0xD02B0000u, 46u}, // lbu -> Latn
-    {0xD82B0000u, 46u}, // lbw -> Latn
-    {0xB04B0000u, 46u}, // lcm -> Latn
-    {0xBC4B0000u, 91u}, // lcp -> Thai
-    {0x846B0000u, 46u}, // ldb -> Latn
-    {0x8C8B0000u, 46u}, // led -> Latn
-    {0x908B0000u, 46u}, // lee -> Latn
-    {0xB08B0000u, 46u}, // lem -> Latn
-    {0xBC8B0000u, 47u}, // lep -> Lepc
-    {0xC08B0000u, 46u}, // leq -> Latn
-    {0xD08B0000u, 46u}, // leu -> Latn
+    {0xD02B0000u, 45u}, // lbu -> Latn
+    {0xD82B0000u, 45u}, // lbw -> Latn
+    {0xB04B0000u, 45u}, // lcm -> Latn
+    {0xBC4B0000u, 92u}, // lcp -> Thai
+    {0x846B0000u, 45u}, // ldb -> Latn
+    {0x8C8B0000u, 45u}, // led -> Latn
+    {0x908B0000u, 45u}, // lee -> Latn
+    {0xB08B0000u, 45u}, // lem -> Latn
+    {0xBC8B0000u, 46u}, // lep -> Lepc
+    {0xC08B0000u, 45u}, // leq -> Latn
+    {0xD08B0000u, 45u}, // leu -> Latn
     {0xE48B0000u, 18u}, // lez -> Cyrl
-    {0x6C670000u, 46u}, // lg -> Latn
-    {0x98CB0000u, 46u}, // lgg -> Latn
-    {0x6C690000u, 46u}, // li -> Latn
-    {0x810B0000u, 46u}, // lia -> Latn
-    {0x8D0B0000u, 46u}, // lid -> Latn
+    {0x6C670000u, 45u}, // lg -> Latn
+    {0x98CB0000u, 45u}, // lgg -> Latn
+    {0x6C690000u, 45u}, // li -> Latn
+    {0x810B0000u, 45u}, // lia -> Latn
+    {0x8D0B0000u, 45u}, // lid -> Latn
     {0x950B0000u, 19u}, // lif -> Deva
-    {0x990B0000u, 46u}, // lig -> Latn
-    {0x9D0B0000u, 46u}, // lih -> Latn
-    {0xA50B0000u, 46u}, // lij -> Latn
-    {0xC90B0000u, 49u}, // lis -> Lisu
-    {0xBD2B0000u, 46u}, // ljp -> Latn
+    {0x990B0000u, 45u}, // lig -> Latn
+    {0x9D0B0000u, 45u}, // lih -> Latn
+    {0xA50B0000u, 45u}, // lij -> Latn
+    {0xC90B0000u, 48u}, // lis -> Lisu
+    {0xBD2B0000u, 45u}, // ljp -> Latn
     {0xA14B0000u,  2u}, // lki -> Arab
-    {0xCD4B0000u, 46u}, // lkt -> Latn
-    {0x916B0000u, 46u}, // lle -> Latn
-    {0xB56B0000u, 46u}, // lln -> Latn
-    {0xB58B0000u, 88u}, // lmn -> Telu
-    {0xB98B0000u, 46u}, // lmo -> Latn
-    {0xBD8B0000u, 46u}, // lmp -> Latn
-    {0x6C6E0000u, 46u}, // ln -> Latn
-    {0xC9AB0000u, 46u}, // lns -> Latn
-    {0xD1AB0000u, 46u}, // lnu -> Latn
-    {0x6C6F0000u, 45u}, // lo -> Laoo
-    {0xA5CB0000u, 46u}, // loj -> Latn
-    {0xA9CB0000u, 46u}, // lok -> Latn
-    {0xADCB0000u, 46u}, // lol -> Latn
-    {0xC5CB0000u, 46u}, // lor -> Latn
-    {0xC9CB0000u, 46u}, // los -> Latn
-    {0xE5CB0000u, 46u}, // loz -> Latn
+    {0xCD4B0000u, 45u}, // lkt -> Latn
+    {0x916B0000u, 45u}, // lle -> Latn
+    {0xB56B0000u, 45u}, // lln -> Latn
+    {0xB58B0000u, 89u}, // lmn -> Telu
+    {0xB98B0000u, 45u}, // lmo -> Latn
+    {0xBD8B0000u, 45u}, // lmp -> Latn
+    {0x6C6E0000u, 45u}, // ln -> Latn
+    {0xC9AB0000u, 45u}, // lns -> Latn
+    {0xD1AB0000u, 45u}, // lnu -> Latn
+    {0x6C6F0000u, 44u}, // lo -> Laoo
+    {0xA5CB0000u, 45u}, // loj -> Latn
+    {0xA9CB0000u, 45u}, // lok -> Latn
+    {0xADCB0000u, 45u}, // lol -> Latn
+    {0xC5CB0000u, 45u}, // lor -> Latn
+    {0xC9CB0000u, 45u}, // los -> Latn
+    {0xE5CB0000u, 45u}, // loz -> Latn
     {0x8A2B0000u,  2u}, // lrc -> Arab
-    {0x6C740000u, 46u}, // lt -> Latn
-    {0x9A6B0000u, 46u}, // ltg -> Latn
-    {0x6C750000u, 46u}, // lu -> Latn
-    {0x828B0000u, 46u}, // lua -> Latn
-    {0xBA8B0000u, 46u}, // luo -> Latn
-    {0xE28B0000u, 46u}, // luy -> Latn
+    {0x6C740000u, 45u}, // lt -> Latn
+    {0x9A6B0000u, 45u}, // ltg -> Latn
+    {0x6C750000u, 45u}, // lu -> Latn
+    {0x828B0000u, 45u}, // lua -> Latn
+    {0xBA8B0000u, 45u}, // luo -> Latn
+    {0xE28B0000u, 45u}, // luy -> Latn
     {0xE68B0000u,  2u}, // luz -> Arab
-    {0x6C760000u, 46u}, // lv -> Latn
-    {0xAECB0000u, 91u}, // lwl -> Thai
+    {0x6C760000u, 45u}, // lv -> Latn
+    {0xAECB0000u, 92u}, // lwl -> Thai
     {0x9F2B0000u, 29u}, // lzh -> Hans
-    {0xE72B0000u, 46u}, // lzz -> Latn
-    {0x8C0C0000u, 46u}, // mad -> Latn
-    {0x940C0000u, 46u}, // maf -> Latn
+    {0xE72B0000u, 45u}, // lzz -> Latn
+    {0x8C0C0000u, 45u}, // mad -> Latn
+    {0x940C0000u, 45u}, // maf -> Latn
     {0x980C0000u, 19u}, // mag -> Deva
     {0xA00C0000u, 19u}, // mai -> Deva
-    {0xA80C0000u, 46u}, // mak -> Latn
-    {0xB40C0000u, 46u}, // man -> Latn
-    {0xB40C474Eu, 61u}, // man-GN -> Nkoo
-    {0xC80C0000u, 46u}, // mas -> Latn
-    {0xD80C0000u, 46u}, // maw -> Latn
-    {0xE40C0000u, 46u}, // maz -> Latn
-    {0x9C2C0000u, 46u}, // mbh -> Latn
-    {0xB82C0000u, 46u}, // mbo -> Latn
-    {0xC02C0000u, 46u}, // mbq -> Latn
-    {0xD02C0000u, 46u}, // mbu -> Latn
-    {0xD82C0000u, 46u}, // mbw -> Latn
-    {0xA04C0000u, 46u}, // mci -> Latn
-    {0xBC4C0000u, 46u}, // mcp -> Latn
-    {0xC04C0000u, 46u}, // mcq -> Latn
-    {0xC44C0000u, 46u}, // mcr -> Latn
-    {0xD04C0000u, 46u}, // mcu -> Latn
-    {0x806C0000u, 46u}, // mda -> Latn
+    {0xA80C0000u, 45u}, // mak -> Latn
+    {0xB40C0000u, 45u}, // man -> Latn
+    {0xB40C474Eu, 60u}, // man-GN -> Nkoo
+    {0xC80C0000u, 45u}, // mas -> Latn
+    {0xD80C0000u, 45u}, // maw -> Latn
+    {0xE40C0000u, 45u}, // maz -> Latn
+    {0x9C2C0000u, 45u}, // mbh -> Latn
+    {0xB82C0000u, 45u}, // mbo -> Latn
+    {0xC02C0000u, 45u}, // mbq -> Latn
+    {0xD02C0000u, 45u}, // mbu -> Latn
+    {0xD82C0000u, 45u}, // mbw -> Latn
+    {0xA04C0000u, 45u}, // mci -> Latn
+    {0xBC4C0000u, 45u}, // mcp -> Latn
+    {0xC04C0000u, 45u}, // mcq -> Latn
+    {0xC44C0000u, 45u}, // mcr -> Latn
+    {0xD04C0000u, 45u}, // mcu -> Latn
+    {0x806C0000u, 45u}, // mda -> Latn
     {0x906C0000u,  2u}, // mde -> Arab
     {0x946C0000u, 18u}, // mdf -> Cyrl
-    {0x9C6C0000u, 46u}, // mdh -> Latn
-    {0xA46C0000u, 46u}, // mdj -> Latn
-    {0xC46C0000u, 46u}, // mdr -> Latn
+    {0x9C6C0000u, 45u}, // mdh -> Latn
+    {0xA46C0000u, 45u}, // mdj -> Latn
+    {0xC46C0000u, 45u}, // mdr -> Latn
     {0xDC6C0000u, 21u}, // mdx -> Ethi
-    {0x8C8C0000u, 46u}, // med -> Latn
-    {0x908C0000u, 46u}, // mee -> Latn
-    {0xA88C0000u, 46u}, // mek -> Latn
-    {0xB48C0000u, 46u}, // men -> Latn
-    {0xC48C0000u, 46u}, // mer -> Latn
-    {0xCC8C0000u, 46u}, // met -> Latn
-    {0xD08C0000u, 46u}, // meu -> Latn
+    {0x8C8C0000u, 45u}, // med -> Latn
+    {0x908C0000u, 45u}, // mee -> Latn
+    {0xA88C0000u, 45u}, // mek -> Latn
+    {0xB48C0000u, 45u}, // men -> Latn
+    {0xC48C0000u, 45u}, // mer -> Latn
+    {0xCC8C0000u, 45u}, // met -> Latn
+    {0xD08C0000u, 45u}, // meu -> Latn
     {0x80AC0000u,  2u}, // mfa -> Arab
-    {0x90AC0000u, 46u}, // mfe -> Latn
-    {0xB4AC0000u, 46u}, // mfn -> Latn
-    {0xB8AC0000u, 46u}, // mfo -> Latn
-    {0xC0AC0000u, 46u}, // mfq -> Latn
-    {0x6D670000u, 46u}, // mg -> Latn
-    {0x9CCC0000u, 46u}, // mgh -> Latn
-    {0xACCC0000u, 46u}, // mgl -> Latn
-    {0xB8CC0000u, 46u}, // mgo -> Latn
+    {0x90AC0000u, 45u}, // mfe -> Latn
+    {0xB4AC0000u, 45u}, // mfn -> Latn
+    {0xB8AC0000u, 45u}, // mfo -> Latn
+    {0xC0AC0000u, 45u}, // mfq -> Latn
+    {0x6D670000u, 45u}, // mg -> Latn
+    {0x9CCC0000u, 45u}, // mgh -> Latn
+    {0xACCC0000u, 45u}, // mgl -> Latn
+    {0xB8CC0000u, 45u}, // mgo -> Latn
     {0xBCCC0000u, 19u}, // mgp -> Deva
-    {0xE0CC0000u, 46u}, // mgy -> Latn
-    {0x6D680000u, 46u}, // mh -> Latn
-    {0xA0EC0000u, 46u}, // mhi -> Latn
-    {0xACEC0000u, 46u}, // mhl -> Latn
-    {0x6D690000u, 46u}, // mi -> Latn
-    {0x950C0000u, 46u}, // mif -> Latn
-    {0xB50C0000u, 46u}, // min -> Latn
-    {0xD90C0000u, 46u}, // miw -> Latn
+    {0xE0CC0000u, 45u}, // mgy -> Latn
+    {0x6D680000u, 45u}, // mh -> Latn
+    {0xA0EC0000u, 45u}, // mhi -> Latn
+    {0xACEC0000u, 45u}, // mhl -> Latn
+    {0x6D690000u, 45u}, // mi -> Latn
+    {0x950C0000u, 45u}, // mif -> Latn
+    {0xB50C0000u, 45u}, // min -> Latn
+    {0xD90C0000u, 45u}, // miw -> Latn
     {0x6D6B0000u, 18u}, // mk -> Cyrl
     {0xA14C0000u,  2u}, // mki -> Arab
-    {0xAD4C0000u, 46u}, // mkl -> Latn
-    {0xBD4C0000u, 46u}, // mkp -> Latn
-    {0xD94C0000u, 46u}, // mkw -> Latn
-    {0x6D6C0000u, 56u}, // ml -> Mlym
-    {0x916C0000u, 46u}, // mle -> Latn
-    {0xBD6C0000u, 46u}, // mlp -> Latn
-    {0xC96C0000u, 46u}, // mls -> Latn
-    {0xB98C0000u, 46u}, // mmo -> Latn
-    {0xD18C0000u, 46u}, // mmu -> Latn
-    {0xDD8C0000u, 46u}, // mmx -> Latn
+    {0xAD4C0000u, 45u}, // mkl -> Latn
+    {0xBD4C0000u, 45u}, // mkp -> Latn
+    {0xD94C0000u, 45u}, // mkw -> Latn
+    {0x6D6C0000u, 55u}, // ml -> Mlym
+    {0x916C0000u, 45u}, // mle -> Latn
+    {0xBD6C0000u, 45u}, // mlp -> Latn
+    {0xC96C0000u, 45u}, // mls -> Latn
+    {0xB98C0000u, 45u}, // mmo -> Latn
+    {0xD18C0000u, 45u}, // mmu -> Latn
+    {0xDD8C0000u, 45u}, // mmx -> Latn
     {0x6D6E0000u, 18u}, // mn -> Cyrl
-    {0x6D6E434Eu, 57u}, // mn-CN -> Mong
-    {0x81AC0000u, 46u}, // mna -> Latn
-    {0x95AC0000u, 46u}, // mnf -> Latn
+    {0x6D6E434Eu, 56u}, // mn-CN -> Mong
+    {0x81AC0000u, 45u}, // mna -> Latn
+    {0x95AC0000u, 45u}, // mnf -> Latn
     {0xA1AC0000u,  8u}, // mni -> Beng
-    {0xD9AC0000u, 59u}, // mnw -> Mymr
-    {0x6D6F0000u, 46u}, // mo -> Latn
-    {0x81CC0000u, 46u}, // moa -> Latn
-    {0x91CC0000u, 46u}, // moe -> Latn
-    {0x9DCC0000u, 46u}, // moh -> Latn
-    {0xC9CC0000u, 46u}, // mos -> Latn
-    {0xDDCC0000u, 46u}, // mox -> Latn
-    {0xBDEC0000u, 46u}, // mpp -> Latn
-    {0xC9EC0000u, 46u}, // mps -> Latn
-    {0xCDEC0000u, 46u}, // mpt -> Latn
-    {0xDDEC0000u, 46u}, // mpx -> Latn
-    {0xAE0C0000u, 46u}, // mql -> Latn
+    {0xD9AC0000u, 58u}, // mnw -> Mymr
+    {0x6D6F0000u, 45u}, // mo -> Latn
+    {0x81CC0000u, 45u}, // moa -> Latn
+    {0x91CC0000u, 45u}, // moe -> Latn
+    {0x9DCC0000u, 45u}, // moh -> Latn
+    {0xC9CC0000u, 45u}, // mos -> Latn
+    {0xDDCC0000u, 45u}, // mox -> Latn
+    {0xBDEC0000u, 45u}, // mpp -> Latn
+    {0xC9EC0000u, 45u}, // mps -> Latn
+    {0xCDEC0000u, 45u}, // mpt -> Latn
+    {0xDDEC0000u, 45u}, // mpx -> Latn
+    {0xAE0C0000u, 45u}, // mql -> Latn
     {0x6D720000u, 19u}, // mr -> Deva
     {0x8E2C0000u, 19u}, // mrd -> Deva
     {0xA62C0000u, 18u}, // mrj -> Cyrl
-    {0xBA2C0000u, 58u}, // mro -> Mroo
-    {0x6D730000u, 46u}, // ms -> Latn
+    {0xBA2C0000u, 57u}, // mro -> Mroo
+    {0x6D730000u, 45u}, // ms -> Latn
     {0x6D734343u,  2u}, // ms-CC -> Arab
-    {0x6D740000u, 46u}, // mt -> Latn
-    {0x8A6C0000u, 46u}, // mtc -> Latn
-    {0x966C0000u, 46u}, // mtf -> Latn
-    {0xA26C0000u, 46u}, // mti -> Latn
+    {0x6D740000u, 45u}, // mt -> Latn
+    {0x8A6C0000u, 45u}, // mtc -> Latn
+    {0x966C0000u, 45u}, // mtf -> Latn
+    {0xA26C0000u, 45u}, // mti -> Latn
     {0xC66C0000u, 19u}, // mtr -> Deva
-    {0x828C0000u, 46u}, // mua -> Latn
-    {0xC68C0000u, 46u}, // mur -> Latn
-    {0xCA8C0000u, 46u}, // mus -> Latn
-    {0x82AC0000u, 46u}, // mva -> Latn
-    {0xB6AC0000u, 46u}, // mvn -> Latn
+    {0x828C0000u, 45u}, // mua -> Latn
+    {0xC68C0000u, 45u}, // mur -> Latn
+    {0xCA8C0000u, 45u}, // mus -> Latn
+    {0x82AC0000u, 45u}, // mva -> Latn
+    {0xB6AC0000u, 45u}, // mvn -> Latn
     {0xE2AC0000u,  2u}, // mvy -> Arab
-    {0xAACC0000u, 46u}, // mwk -> Latn
+    {0xAACC0000u, 45u}, // mwk -> Latn
     {0xC6CC0000u, 19u}, // mwr -> Deva
-    {0xD6CC0000u, 46u}, // mwv -> Latn
-    {0xDACC0000u, 34u}, // mww -> Hmnp
-    {0x8AEC0000u, 46u}, // mxc -> Latn
-    {0xB2EC0000u, 46u}, // mxm -> Latn
-    {0x6D790000u, 59u}, // my -> Mymr
-    {0xAB0C0000u, 46u}, // myk -> Latn
+    {0xD6CC0000u, 45u}, // mwv -> Latn
+    {0xDACC0000u, 33u}, // mww -> Hmnp
+    {0x8AEC0000u, 45u}, // mxc -> Latn
+    {0xB2EC0000u, 45u}, // mxm -> Latn
+    {0x6D790000u, 58u}, // my -> Mymr
+    {0xAB0C0000u, 45u}, // myk -> Latn
     {0xB30C0000u, 21u}, // mym -> Ethi
     {0xD70C0000u, 18u}, // myv -> Cyrl
-    {0xDB0C0000u, 46u}, // myw -> Latn
-    {0xDF0C0000u, 46u}, // myx -> Latn
-    {0xE70C0000u, 52u}, // myz -> Mand
-    {0xAB2C0000u, 46u}, // mzk -> Latn
-    {0xB32C0000u, 46u}, // mzm -> Latn
+    {0xDB0C0000u, 45u}, // myw -> Latn
+    {0xDF0C0000u, 45u}, // myx -> Latn
+    {0xE70C0000u, 51u}, // myz -> Mand
+    {0xAB2C0000u, 45u}, // mzk -> Latn
+    {0xB32C0000u, 45u}, // mzm -> Latn
     {0xB72C0000u,  2u}, // mzn -> Arab
-    {0xBF2C0000u, 46u}, // mzp -> Latn
-    {0xDB2C0000u, 46u}, // mzw -> Latn
-    {0xE72C0000u, 46u}, // mzz -> Latn
-    {0x6E610000u, 46u}, // na -> Latn
-    {0x880D0000u, 46u}, // nac -> Latn
-    {0x940D0000u, 46u}, // naf -> Latn
-    {0xA80D0000u, 46u}, // nak -> Latn
+    {0xBF2C0000u, 45u}, // mzp -> Latn
+    {0xDB2C0000u, 45u}, // mzw -> Latn
+    {0xE72C0000u, 45u}, // mzz -> Latn
+    {0x6E610000u, 45u}, // na -> Latn
+    {0x880D0000u, 45u}, // nac -> Latn
+    {0x940D0000u, 45u}, // naf -> Latn
+    {0xA80D0000u, 45u}, // nak -> Latn
     {0xB40D0000u, 29u}, // nan -> Hans
-    {0xBC0D0000u, 46u}, // nap -> Latn
-    {0xC00D0000u, 46u}, // naq -> Latn
-    {0xC80D0000u, 46u}, // nas -> Latn
-    {0x6E620000u, 46u}, // nb -> Latn
-    {0x804D0000u, 46u}, // nca -> Latn
-    {0x904D0000u, 46u}, // nce -> Latn
-    {0x944D0000u, 46u}, // ncf -> Latn
-    {0x9C4D0000u, 46u}, // nch -> Latn
-    {0xB84D0000u, 46u}, // nco -> Latn
-    {0xD04D0000u, 46u}, // ncu -> Latn
-    {0x6E640000u, 46u}, // nd -> Latn
-    {0x886D0000u, 46u}, // ndc -> Latn
-    {0xC86D0000u, 46u}, // nds -> Latn
+    {0xBC0D0000u, 45u}, // nap -> Latn
+    {0xC00D0000u, 45u}, // naq -> Latn
+    {0xC80D0000u, 45u}, // nas -> Latn
+    {0x6E620000u, 45u}, // nb -> Latn
+    {0x804D0000u, 45u}, // nca -> Latn
+    {0x904D0000u, 45u}, // nce -> Latn
+    {0x944D0000u, 45u}, // ncf -> Latn
+    {0x9C4D0000u, 45u}, // nch -> Latn
+    {0xB84D0000u, 45u}, // nco -> Latn
+    {0xD04D0000u, 45u}, // ncu -> Latn
+    {0x6E640000u, 45u}, // nd -> Latn
+    {0x886D0000u, 45u}, // ndc -> Latn
+    {0xC86D0000u, 45u}, // nds -> Latn
     {0x6E650000u, 19u}, // ne -> Deva
-    {0x848D0000u, 46u}, // neb -> Latn
+    {0x848D0000u, 45u}, // neb -> Latn
     {0xD88D0000u, 19u}, // new -> Deva
-    {0xDC8D0000u, 46u}, // nex -> Latn
-    {0xC4AD0000u, 46u}, // nfr -> Latn
-    {0x6E670000u, 46u}, // ng -> Latn
-    {0x80CD0000u, 46u}, // nga -> Latn
-    {0x84CD0000u, 46u}, // ngb -> Latn
-    {0xACCD0000u, 46u}, // ngl -> Latn
-    {0x84ED0000u, 46u}, // nhb -> Latn
-    {0x90ED0000u, 46u}, // nhe -> Latn
-    {0xD8ED0000u, 46u}, // nhw -> Latn
-    {0x950D0000u, 46u}, // nif -> Latn
-    {0xA10D0000u, 46u}, // nii -> Latn
-    {0xA50D0000u, 46u}, // nij -> Latn
-    {0xB50D0000u, 46u}, // nin -> Latn
-    {0xD10D0000u, 46u}, // niu -> Latn
-    {0xE10D0000u, 46u}, // niy -> Latn
-    {0xE50D0000u, 46u}, // niz -> Latn
-    {0xB92D0000u, 46u}, // njo -> Latn
-    {0x994D0000u, 46u}, // nkg -> Latn
-    {0xB94D0000u, 46u}, // nko -> Latn
-    {0x6E6C0000u, 46u}, // nl -> Latn
-    {0x998D0000u, 46u}, // nmg -> Latn
-    {0xE58D0000u, 46u}, // nmz -> Latn
-    {0x6E6E0000u, 46u}, // nn -> Latn
-    {0x95AD0000u, 46u}, // nnf -> Latn
-    {0x9DAD0000u, 46u}, // nnh -> Latn
-    {0xA9AD0000u, 46u}, // nnk -> Latn
-    {0xB1AD0000u, 46u}, // nnm -> Latn
-    {0xBDAD0000u, 95u}, // nnp -> Wcho
-    {0x6E6F0000u, 46u}, // no -> Latn
-    {0x8DCD0000u, 44u}, // nod -> Lana
+    {0xDC8D0000u, 45u}, // nex -> Latn
+    {0xC4AD0000u, 45u}, // nfr -> Latn
+    {0x6E670000u, 45u}, // ng -> Latn
+    {0x80CD0000u, 45u}, // nga -> Latn
+    {0x84CD0000u, 45u}, // ngb -> Latn
+    {0xACCD0000u, 45u}, // ngl -> Latn
+    {0x84ED0000u, 45u}, // nhb -> Latn
+    {0x90ED0000u, 45u}, // nhe -> Latn
+    {0xD8ED0000u, 45u}, // nhw -> Latn
+    {0x950D0000u, 45u}, // nif -> Latn
+    {0xA10D0000u, 45u}, // nii -> Latn
+    {0xA50D0000u, 45u}, // nij -> Latn
+    {0xB50D0000u, 45u}, // nin -> Latn
+    {0xD10D0000u, 45u}, // niu -> Latn
+    {0xE10D0000u, 45u}, // niy -> Latn
+    {0xE50D0000u, 45u}, // niz -> Latn
+    {0xB92D0000u, 45u}, // njo -> Latn
+    {0x994D0000u, 45u}, // nkg -> Latn
+    {0xB94D0000u, 45u}, // nko -> Latn
+    {0x6E6C0000u, 45u}, // nl -> Latn
+    {0x998D0000u, 45u}, // nmg -> Latn
+    {0xE58D0000u, 45u}, // nmz -> Latn
+    {0x6E6E0000u, 45u}, // nn -> Latn
+    {0x95AD0000u, 45u}, // nnf -> Latn
+    {0x9DAD0000u, 45u}, // nnh -> Latn
+    {0xA9AD0000u, 45u}, // nnk -> Latn
+    {0xB1AD0000u, 45u}, // nnm -> Latn
+    {0xBDAD0000u, 98u}, // nnp -> Wcho
+    {0x6E6F0000u, 45u}, // no -> Latn
+    {0x8DCD0000u, 43u}, // nod -> Lana
     {0x91CD0000u, 19u}, // noe -> Deva
-    {0xB5CD0000u, 73u}, // non -> Runr
-    {0xBDCD0000u, 46u}, // nop -> Latn
-    {0xD1CD0000u, 46u}, // nou -> Latn
-    {0xBA0D0000u, 61u}, // nqo -> Nkoo
-    {0x6E720000u, 46u}, // nr -> Latn
-    {0x862D0000u, 46u}, // nrb -> Latn
+    {0xB5CD0000u, 74u}, // non -> Runr
+    {0xBDCD0000u, 45u}, // nop -> Latn
+    {0xD1CD0000u, 45u}, // nou -> Latn
+    {0xBA0D0000u, 60u}, // nqo -> Nkoo
+    {0x6E720000u, 45u}, // nr -> Latn
+    {0x862D0000u, 45u}, // nrb -> Latn
     {0xAA4D0000u, 11u}, // nsk -> Cans
-    {0xB64D0000u, 46u}, // nsn -> Latn
-    {0xBA4D0000u, 46u}, // nso -> Latn
-    {0xCA4D0000u, 46u}, // nss -> Latn
-    {0xB26D0000u, 46u}, // ntm -> Latn
-    {0xC66D0000u, 46u}, // ntr -> Latn
-    {0xA28D0000u, 46u}, // nui -> Latn
-    {0xBE8D0000u, 46u}, // nup -> Latn
-    {0xCA8D0000u, 46u}, // nus -> Latn
-    {0xD68D0000u, 46u}, // nuv -> Latn
-    {0xDE8D0000u, 46u}, // nux -> Latn
-    {0x6E760000u, 46u}, // nv -> Latn
-    {0x86CD0000u, 46u}, // nwb -> Latn
-    {0xC2ED0000u, 46u}, // nxq -> Latn
-    {0xC6ED0000u, 46u}, // nxr -> Latn
-    {0x6E790000u, 46u}, // ny -> Latn
-    {0xB30D0000u, 46u}, // nym -> Latn
-    {0xB70D0000u, 46u}, // nyn -> Latn
-    {0xA32D0000u, 46u}, // nzi -> Latn
-    {0x6F630000u, 46u}, // oc -> Latn
-    {0x88CE0000u, 46u}, // ogc -> Latn
-    {0xC54E0000u, 46u}, // okr -> Latn
-    {0xD54E0000u, 46u}, // okv -> Latn
-    {0x6F6D0000u, 46u}, // om -> Latn
-    {0x99AE0000u, 46u}, // ong -> Latn
-    {0xB5AE0000u, 46u}, // onn -> Latn
-    {0xC9AE0000u, 46u}, // ons -> Latn
-    {0xB1EE0000u, 46u}, // opm -> Latn
-    {0x6F720000u, 66u}, // or -> Orya
-    {0xBA2E0000u, 46u}, // oro -> Latn
+    {0xB64D0000u, 45u}, // nsn -> Latn
+    {0xBA4D0000u, 45u}, // nso -> Latn
+    {0xCA4D0000u, 45u}, // nss -> Latn
+    {0xCE4D0000u, 94u}, // nst -> Tnsa
+    {0xB26D0000u, 45u}, // ntm -> Latn
+    {0xC66D0000u, 45u}, // ntr -> Latn
+    {0xA28D0000u, 45u}, // nui -> Latn
+    {0xBE8D0000u, 45u}, // nup -> Latn
+    {0xCA8D0000u, 45u}, // nus -> Latn
+    {0xD68D0000u, 45u}, // nuv -> Latn
+    {0xDE8D0000u, 45u}, // nux -> Latn
+    {0x6E760000u, 45u}, // nv -> Latn
+    {0x86CD0000u, 45u}, // nwb -> Latn
+    {0xC2ED0000u, 45u}, // nxq -> Latn
+    {0xC6ED0000u, 45u}, // nxr -> Latn
+    {0x6E790000u, 45u}, // ny -> Latn
+    {0xB30D0000u, 45u}, // nym -> Latn
+    {0xB70D0000u, 45u}, // nyn -> Latn
+    {0xA32D0000u, 45u}, // nzi -> Latn
+    {0x6F630000u, 45u}, // oc -> Latn
+    {0x88CE0000u, 45u}, // ogc -> Latn
+    {0xC54E0000u, 45u}, // okr -> Latn
+    {0xD54E0000u, 45u}, // okv -> Latn
+    {0x6F6D0000u, 45u}, // om -> Latn
+    {0x99AE0000u, 45u}, // ong -> Latn
+    {0xB5AE0000u, 45u}, // onn -> Latn
+    {0xC9AE0000u, 45u}, // ons -> Latn
+    {0xB1EE0000u, 45u}, // opm -> Latn
+    {0x6F720000u, 65u}, // or -> Orya
+    {0xBA2E0000u, 45u}, // oro -> Latn
     {0xD22E0000u,  2u}, // oru -> Arab
     {0x6F730000u, 18u}, // os -> Cyrl
-    {0x824E0000u, 67u}, // osa -> Osge
+    {0x824E0000u, 66u}, // osa -> Osge
     {0x826E0000u,  2u}, // ota -> Arab
-    {0xAA6E0000u, 65u}, // otk -> Orkh
-    {0xB32E0000u, 46u}, // ozm -> Latn
+    {0xAA6E0000u, 64u}, // otk -> Orkh
+    {0xA28E0000u, 67u}, // oui -> Ougr
+    {0xB32E0000u, 45u}, // ozm -> Latn
     {0x70610000u, 28u}, // pa -> Guru
     {0x7061504Bu,  2u}, // pa-PK -> Arab
-    {0x980F0000u, 46u}, // pag -> Latn
+    {0x980F0000u, 45u}, // pag -> Latn
     {0xAC0F0000u, 69u}, // pal -> Phli
-    {0xB00F0000u, 46u}, // pam -> Latn
-    {0xBC0F0000u, 46u}, // pap -> Latn
-    {0xD00F0000u, 46u}, // pau -> Latn
-    {0xA02F0000u, 46u}, // pbi -> Latn
-    {0x8C4F0000u, 46u}, // pcd -> Latn
-    {0xB04F0000u, 46u}, // pcm -> Latn
-    {0x886F0000u, 46u}, // pdc -> Latn
-    {0xCC6F0000u, 46u}, // pdt -> Latn
-    {0x8C8F0000u, 46u}, // ped -> Latn
-    {0xB88F0000u, 96u}, // peo -> Xpeo
-    {0xDC8F0000u, 46u}, // pex -> Latn
-    {0xACAF0000u, 46u}, // pfl -> Latn
+    {0xB00F0000u, 45u}, // pam -> Latn
+    {0xBC0F0000u, 45u}, // pap -> Latn
+    {0xD00F0000u, 45u}, // pau -> Latn
+    {0xA02F0000u, 45u}, // pbi -> Latn
+    {0x8C4F0000u, 45u}, // pcd -> Latn
+    {0xB04F0000u, 45u}, // pcm -> Latn
+    {0x886F0000u, 45u}, // pdc -> Latn
+    {0xCC6F0000u, 45u}, // pdt -> Latn
+    {0x8C8F0000u, 45u}, // ped -> Latn
+    {0xB88F0000u, 99u}, // peo -> Xpeo
+    {0xDC8F0000u, 45u}, // pex -> Latn
+    {0xACAF0000u, 45u}, // pfl -> Latn
     {0xACEF0000u,  2u}, // phl -> Arab
     {0xB4EF0000u, 70u}, // phn -> Phnx
-    {0xAD0F0000u, 46u}, // pil -> Latn
-    {0xBD0F0000u, 46u}, // pip -> Latn
+    {0xAD0F0000u, 45u}, // pil -> Latn
+    {0xBD0F0000u, 45u}, // pip -> Latn
     {0x814F0000u,  9u}, // pka -> Brah
-    {0xB94F0000u, 46u}, // pko -> Latn
-    {0x706C0000u, 46u}, // pl -> Latn
-    {0x816F0000u, 46u}, // pla -> Latn
-    {0xC98F0000u, 46u}, // pms -> Latn
-    {0x99AF0000u, 46u}, // png -> Latn
-    {0xB5AF0000u, 46u}, // pnn -> Latn
+    {0xB94F0000u, 45u}, // pko -> Latn
+    {0x706C0000u, 45u}, // pl -> Latn
+    {0x816F0000u, 45u}, // pla -> Latn
+    {0xC98F0000u, 45u}, // pms -> Latn
+    {0x99AF0000u, 45u}, // png -> Latn
+    {0xB5AF0000u, 45u}, // pnn -> Latn
     {0xCDAF0000u, 26u}, // pnt -> Grek
-    {0xB5CF0000u, 46u}, // pon -> Latn
+    {0xB5CF0000u, 45u}, // pon -> Latn
     {0x81EF0000u, 19u}, // ppa -> Deva
-    {0xB9EF0000u, 46u}, // ppo -> Latn
-    {0x822F0000u, 39u}, // pra -> Khar
+    {0xB9EF0000u, 45u}, // ppo -> Latn
+    {0x822F0000u, 38u}, // pra -> Khar
     {0x8E2F0000u,  2u}, // prd -> Arab
-    {0x9A2F0000u, 46u}, // prg -> Latn
+    {0x9A2F0000u, 45u}, // prg -> Latn
     {0x70730000u,  2u}, // ps -> Arab
-    {0xCA4F0000u, 46u}, // pss -> Latn
-    {0x70740000u, 46u}, // pt -> Latn
-    {0xBE6F0000u, 46u}, // ptp -> Latn
-    {0xD28F0000u, 46u}, // puu -> Latn
-    {0x82CF0000u, 46u}, // pwa -> Latn
-    {0x71750000u, 46u}, // qu -> Latn
-    {0x8A900000u, 46u}, // quc -> Latn
-    {0x9A900000u, 46u}, // qug -> Latn
-    {0xA0110000u, 46u}, // rai -> Latn
+    {0xCA4F0000u, 45u}, // pss -> Latn
+    {0x70740000u, 45u}, // pt -> Latn
+    {0xBE6F0000u, 45u}, // ptp -> Latn
+    {0xD28F0000u, 45u}, // puu -> Latn
+    {0x82CF0000u, 45u}, // pwa -> Latn
+    {0x71750000u, 45u}, // qu -> Latn
+    {0x8A900000u, 45u}, // quc -> Latn
+    {0x9A900000u, 45u}, // qug -> Latn
+    {0xA0110000u, 45u}, // rai -> Latn
     {0xA4110000u, 19u}, // raj -> Deva
-    {0xB8110000u, 46u}, // rao -> Latn
-    {0x94510000u, 46u}, // rcf -> Latn
-    {0xA4910000u, 46u}, // rej -> Latn
-    {0xAC910000u, 46u}, // rel -> Latn
-    {0xC8910000u, 46u}, // res -> Latn
-    {0xB4D10000u, 46u}, // rgn -> Latn
-    {0x98F10000u,  2u}, // rhg -> Arab
-    {0x81110000u, 46u}, // ria -> Latn
-    {0x95110000u, 89u}, // rif -> Tfng
-    {0x95114E4Cu, 46u}, // rif-NL -> Latn
+    {0xB8110000u, 45u}, // rao -> Latn
+    {0x94510000u, 45u}, // rcf -> Latn
+    {0xA4910000u, 45u}, // rej -> Latn
+    {0xAC910000u, 45u}, // rel -> Latn
+    {0xC8910000u, 45u}, // res -> Latn
+    {0xB4D10000u, 45u}, // rgn -> Latn
+    {0x98F10000u, 73u}, // rhg -> Rohg
+    {0x81110000u, 45u}, // ria -> Latn
+    {0x95110000u, 90u}, // rif -> Tfng
+    {0x95114E4Cu, 45u}, // rif-NL -> Latn
     {0xC9310000u, 19u}, // rjs -> Deva
     {0xCD510000u,  8u}, // rkt -> Beng
-    {0x726D0000u, 46u}, // rm -> Latn
-    {0x95910000u, 46u}, // rmf -> Latn
-    {0xB9910000u, 46u}, // rmo -> Latn
+    {0x726D0000u, 45u}, // rm -> Latn
+    {0x95910000u, 45u}, // rmf -> Latn
+    {0xB9910000u, 45u}, // rmo -> Latn
     {0xCD910000u,  2u}, // rmt -> Arab
-    {0xD1910000u, 46u}, // rmu -> Latn
-    {0x726E0000u, 46u}, // rn -> Latn
-    {0x81B10000u, 46u}, // rna -> Latn
-    {0x99B10000u, 46u}, // rng -> Latn
-    {0x726F0000u, 46u}, // ro -> Latn
-    {0x85D10000u, 46u}, // rob -> Latn
-    {0x95D10000u, 46u}, // rof -> Latn
-    {0xB9D10000u, 46u}, // roo -> Latn
-    {0xBA310000u, 46u}, // rro -> Latn
-    {0xB2710000u, 46u}, // rtm -> Latn
+    {0xD1910000u, 45u}, // rmu -> Latn
+    {0x726E0000u, 45u}, // rn -> Latn
+    {0x81B10000u, 45u}, // rna -> Latn
+    {0x99B10000u, 45u}, // rng -> Latn
+    {0x726F0000u, 45u}, // ro -> Latn
+    {0x85D10000u, 45u}, // rob -> Latn
+    {0x95D10000u, 45u}, // rof -> Latn
+    {0xB9D10000u, 45u}, // roo -> Latn
+    {0xBA310000u, 45u}, // rro -> Latn
+    {0xB2710000u, 45u}, // rtm -> Latn
     {0x72750000u, 18u}, // ru -> Cyrl
     {0x92910000u, 18u}, // rue -> Cyrl
-    {0x9A910000u, 46u}, // rug -> Latn
-    {0x72770000u, 46u}, // rw -> Latn
-    {0xAAD10000u, 46u}, // rwk -> Latn
-    {0xBAD10000u, 46u}, // rwo -> Latn
-    {0xD3110000u, 38u}, // ryu -> Kana
+    {0x9A910000u, 45u}, // rug -> Latn
+    {0x72770000u, 45u}, // rw -> Latn
+    {0xAAD10000u, 45u}, // rwk -> Latn
+    {0xBAD10000u, 45u}, // rwo -> Latn
+    {0xD3110000u, 37u}, // ryu -> Kana
     {0x73610000u, 19u}, // sa -> Deva
-    {0x94120000u, 46u}, // saf -> Latn
+    {0x94120000u, 45u}, // saf -> Latn
     {0x9C120000u, 18u}, // sah -> Cyrl
-    {0xC0120000u, 46u}, // saq -> Latn
-    {0xC8120000u, 46u}, // sas -> Latn
-    {0xCC120000u, 64u}, // sat -> Olck
-    {0xD4120000u, 46u}, // sav -> Latn
-    {0xE4120000u, 76u}, // saz -> Saur
-    {0x80320000u, 46u}, // sba -> Latn
-    {0x90320000u, 46u}, // sbe -> Latn
-    {0xBC320000u, 46u}, // sbp -> Latn
-    {0x73630000u, 46u}, // sc -> Latn
+    {0xC0120000u, 45u}, // saq -> Latn
+    {0xC8120000u, 45u}, // sas -> Latn
+    {0xCC120000u, 63u}, // sat -> Olck
+    {0xD4120000u, 45u}, // sav -> Latn
+    {0xE4120000u, 77u}, // saz -> Saur
+    {0x80320000u, 45u}, // sba -> Latn
+    {0x90320000u, 45u}, // sbe -> Latn
+    {0xBC320000u, 45u}, // sbp -> Latn
+    {0x73630000u, 45u}, // sc -> Latn
     {0xA8520000u, 19u}, // sck -> Deva
     {0xAC520000u,  2u}, // scl -> Arab
-    {0xB4520000u, 46u}, // scn -> Latn
-    {0xB8520000u, 46u}, // sco -> Latn
-    {0xC8520000u, 46u}, // scs -> Latn
+    {0xB4520000u, 45u}, // scn -> Latn
+    {0xB8520000u, 45u}, // sco -> Latn
+    {0xC8520000u, 45u}, // scs -> Latn
     {0x73640000u,  2u}, // sd -> Arab
-    {0x88720000u, 46u}, // sdc -> Latn
+    {0x88720000u, 45u}, // sdc -> Latn
     {0x9C720000u,  2u}, // sdh -> Arab
-    {0x73650000u, 46u}, // se -> Latn
-    {0x94920000u, 46u}, // sef -> Latn
-    {0x9C920000u, 46u}, // seh -> Latn
-    {0xA0920000u, 46u}, // sei -> Latn
-    {0xC8920000u, 46u}, // ses -> Latn
-    {0x73670000u, 46u}, // sg -> Latn
-    {0x80D20000u, 63u}, // sga -> Ogam
-    {0xC8D20000u, 46u}, // sgs -> Latn
+    {0x73650000u, 45u}, // se -> Latn
+    {0x94920000u, 45u}, // sef -> Latn
+    {0x9C920000u, 45u}, // seh -> Latn
+    {0xA0920000u, 45u}, // sei -> Latn
+    {0xC8920000u, 45u}, // ses -> Latn
+    {0x73670000u, 45u}, // sg -> Latn
+    {0x80D20000u, 62u}, // sga -> Ogam
+    {0xC8D20000u, 45u}, // sgs -> Latn
     {0xD8D20000u, 21u}, // sgw -> Ethi
-    {0xE4D20000u, 46u}, // sgz -> Latn
-    {0x73680000u, 46u}, // sh -> Latn
-    {0xA0F20000u, 89u}, // shi -> Tfng
-    {0xA8F20000u, 46u}, // shk -> Latn
-    {0xB4F20000u, 59u}, // shn -> Mymr
+    {0xE4D20000u, 45u}, // sgz -> Latn
+    {0x73680000u, 45u}, // sh -> Latn
+    {0xA0F20000u, 90u}, // shi -> Tfng
+    {0xA8F20000u, 45u}, // shk -> Latn
+    {0xB4F20000u, 58u}, // shn -> Mymr
     {0xD0F20000u,  2u}, // shu -> Arab
-    {0x73690000u, 78u}, // si -> Sinh
-    {0x8D120000u, 46u}, // sid -> Latn
-    {0x99120000u, 46u}, // sig -> Latn
-    {0xAD120000u, 46u}, // sil -> Latn
-    {0xB1120000u, 46u}, // sim -> Latn
-    {0xC5320000u, 46u}, // sjr -> Latn
-    {0x736B0000u, 46u}, // sk -> Latn
-    {0x89520000u, 46u}, // skc -> Latn
+    {0x73690000u, 79u}, // si -> Sinh
+    {0x8D120000u, 45u}, // sid -> Latn
+    {0x99120000u, 45u}, // sig -> Latn
+    {0xAD120000u, 45u}, // sil -> Latn
+    {0xB1120000u, 45u}, // sim -> Latn
+    {0xC5320000u, 45u}, // sjr -> Latn
+    {0x736B0000u, 45u}, // sk -> Latn
+    {0x89520000u, 45u}, // skc -> Latn
     {0xC5520000u,  2u}, // skr -> Arab
-    {0xC9520000u, 46u}, // sks -> Latn
-    {0x736C0000u, 46u}, // sl -> Latn
-    {0x8D720000u, 46u}, // sld -> Latn
-    {0xA1720000u, 46u}, // sli -> Latn
-    {0xAD720000u, 46u}, // sll -> Latn
-    {0xE1720000u, 46u}, // sly -> Latn
-    {0x736D0000u, 46u}, // sm -> Latn
-    {0x81920000u, 46u}, // sma -> Latn
-    {0xA5920000u, 46u}, // smj -> Latn
-    {0xB5920000u, 46u}, // smn -> Latn
-    {0xBD920000u, 74u}, // smp -> Samr
-    {0xC1920000u, 46u}, // smq -> Latn
-    {0xC9920000u, 46u}, // sms -> Latn
-    {0x736E0000u, 46u}, // sn -> Latn
-    {0x89B20000u, 46u}, // snc -> Latn
-    {0xA9B20000u, 46u}, // snk -> Latn
-    {0xBDB20000u, 46u}, // snp -> Latn
-    {0xDDB20000u, 46u}, // snx -> Latn
-    {0xE1B20000u, 46u}, // sny -> Latn
-    {0x736F0000u, 46u}, // so -> Latn
-    {0x99D20000u, 79u}, // sog -> Sogd
-    {0xA9D20000u, 46u}, // sok -> Latn
-    {0xC1D20000u, 46u}, // soq -> Latn
-    {0xD1D20000u, 91u}, // sou -> Thai
-    {0xE1D20000u, 46u}, // soy -> Latn
-    {0x8DF20000u, 46u}, // spd -> Latn
-    {0xADF20000u, 46u}, // spl -> Latn
-    {0xC9F20000u, 46u}, // sps -> Latn
-    {0x73710000u, 46u}, // sq -> Latn
+    {0xC9520000u, 45u}, // sks -> Latn
+    {0x736C0000u, 45u}, // sl -> Latn
+    {0x8D720000u, 45u}, // sld -> Latn
+    {0xA1720000u, 45u}, // sli -> Latn
+    {0xAD720000u, 45u}, // sll -> Latn
+    {0xE1720000u, 45u}, // sly -> Latn
+    {0x736D0000u, 45u}, // sm -> Latn
+    {0x81920000u, 45u}, // sma -> Latn
+    {0xA5920000u, 45u}, // smj -> Latn
+    {0xB5920000u, 45u}, // smn -> Latn
+    {0xBD920000u, 75u}, // smp -> Samr
+    {0xC1920000u, 45u}, // smq -> Latn
+    {0xC9920000u, 45u}, // sms -> Latn
+    {0x736E0000u, 45u}, // sn -> Latn
+    {0x89B20000u, 45u}, // snc -> Latn
+    {0xA9B20000u, 45u}, // snk -> Latn
+    {0xBDB20000u, 45u}, // snp -> Latn
+    {0xDDB20000u, 45u}, // snx -> Latn
+    {0xE1B20000u, 45u}, // sny -> Latn
+    {0x736F0000u, 45u}, // so -> Latn
+    {0x99D20000u, 80u}, // sog -> Sogd
+    {0xA9D20000u, 45u}, // sok -> Latn
+    {0xC1D20000u, 45u}, // soq -> Latn
+    {0xD1D20000u, 92u}, // sou -> Thai
+    {0xE1D20000u, 45u}, // soy -> Latn
+    {0x8DF20000u, 45u}, // spd -> Latn
+    {0xADF20000u, 45u}, // spl -> Latn
+    {0xC9F20000u, 45u}, // sps -> Latn
+    {0x73710000u, 45u}, // sq -> Latn
     {0x73720000u, 18u}, // sr -> Cyrl
-    {0x73724D45u, 46u}, // sr-ME -> Latn
-    {0x7372524Fu, 46u}, // sr-RO -> Latn
-    {0x73725255u, 46u}, // sr-RU -> Latn
-    {0x73725452u, 46u}, // sr-TR -> Latn
-    {0x86320000u, 80u}, // srb -> Sora
-    {0xB6320000u, 46u}, // srn -> Latn
-    {0xC6320000u, 46u}, // srr -> Latn
+    {0x73724D45u, 45u}, // sr-ME -> Latn
+    {0x7372524Fu, 45u}, // sr-RO -> Latn
+    {0x73725255u, 45u}, // sr-RU -> Latn
+    {0x73725452u, 45u}, // sr-TR -> Latn
+    {0x86320000u, 81u}, // srb -> Sora
+    {0xB6320000u, 45u}, // srn -> Latn
+    {0xC6320000u, 45u}, // srr -> Latn
     {0xDE320000u, 19u}, // srx -> Deva
-    {0x73730000u, 46u}, // ss -> Latn
-    {0x8E520000u, 46u}, // ssd -> Latn
-    {0x9A520000u, 46u}, // ssg -> Latn
-    {0xE2520000u, 46u}, // ssy -> Latn
-    {0x73740000u, 46u}, // st -> Latn
-    {0xAA720000u, 46u}, // stk -> Latn
-    {0xC2720000u, 46u}, // stq -> Latn
-    {0x73750000u, 46u}, // su -> Latn
-    {0x82920000u, 46u}, // sua -> Latn
-    {0x92920000u, 46u}, // sue -> Latn
-    {0xAA920000u, 46u}, // suk -> Latn
-    {0xC6920000u, 46u}, // sur -> Latn
-    {0xCA920000u, 46u}, // sus -> Latn
-    {0x73760000u, 46u}, // sv -> Latn
-    {0x73770000u, 46u}, // sw -> Latn
+    {0x73730000u, 45u}, // ss -> Latn
+    {0x8E520000u, 45u}, // ssd -> Latn
+    {0x9A520000u, 45u}, // ssg -> Latn
+    {0xE2520000u, 45u}, // ssy -> Latn
+    {0x73740000u, 45u}, // st -> Latn
+    {0xAA720000u, 45u}, // stk -> Latn
+    {0xC2720000u, 45u}, // stq -> Latn
+    {0x73750000u, 45u}, // su -> Latn
+    {0x82920000u, 45u}, // sua -> Latn
+    {0x92920000u, 45u}, // sue -> Latn
+    {0xAA920000u, 45u}, // suk -> Latn
+    {0xC6920000u, 45u}, // sur -> Latn
+    {0xCA920000u, 45u}, // sus -> Latn
+    {0x73760000u, 45u}, // sv -> Latn
+    {0x73770000u, 45u}, // sw -> Latn
     {0x86D20000u,  2u}, // swb -> Arab
-    {0x8AD20000u, 46u}, // swc -> Latn
-    {0x9AD20000u, 46u}, // swg -> Latn
-    {0xBED20000u, 46u}, // swp -> Latn
+    {0x8AD20000u, 45u}, // swc -> Latn
+    {0x9AD20000u, 45u}, // swg -> Latn
+    {0xBED20000u, 45u}, // swp -> Latn
     {0xD6D20000u, 19u}, // swv -> Deva
-    {0xB6F20000u, 46u}, // sxn -> Latn
-    {0xDAF20000u, 46u}, // sxw -> Latn
+    {0xB6F20000u, 45u}, // sxn -> Latn
+    {0xDAF20000u, 45u}, // sxw -> Latn
     {0xAF120000u,  8u}, // syl -> Beng
-    {0xC7120000u, 82u}, // syr -> Syrc
-    {0xAF320000u, 46u}, // szl -> Latn
-    {0x74610000u, 85u}, // ta -> Taml
+    {0xC7120000u, 83u}, // syr -> Syrc
+    {0xAF320000u, 45u}, // szl -> Latn
+    {0x74610000u, 86u}, // ta -> Taml
     {0xA4130000u, 19u}, // taj -> Deva
-    {0xAC130000u, 46u}, // tal -> Latn
-    {0xB4130000u, 46u}, // tan -> Latn
-    {0xC0130000u, 46u}, // taq -> Latn
-    {0x88330000u, 46u}, // tbc -> Latn
-    {0x8C330000u, 46u}, // tbd -> Latn
-    {0x94330000u, 46u}, // tbf -> Latn
-    {0x98330000u, 46u}, // tbg -> Latn
-    {0xB8330000u, 46u}, // tbo -> Latn
-    {0xD8330000u, 46u}, // tbw -> Latn
-    {0xE4330000u, 46u}, // tbz -> Latn
-    {0xA0530000u, 46u}, // tci -> Latn
-    {0xE0530000u, 42u}, // tcy -> Knda
-    {0x8C730000u, 83u}, // tdd -> Tale
+    {0xAC130000u, 45u}, // tal -> Latn
+    {0xB4130000u, 45u}, // tan -> Latn
+    {0xC0130000u, 45u}, // taq -> Latn
+    {0x88330000u, 45u}, // tbc -> Latn
+    {0x8C330000u, 45u}, // tbd -> Latn
+    {0x94330000u, 45u}, // tbf -> Latn
+    {0x98330000u, 45u}, // tbg -> Latn
+    {0xB8330000u, 45u}, // tbo -> Latn
+    {0xD8330000u, 45u}, // tbw -> Latn
+    {0xE4330000u, 45u}, // tbz -> Latn
+    {0xA0530000u, 45u}, // tci -> Latn
+    {0xE0530000u, 41u}, // tcy -> Knda
+    {0x8C730000u, 84u}, // tdd -> Tale
     {0x98730000u, 19u}, // tdg -> Deva
     {0x9C730000u, 19u}, // tdh -> Deva
-    {0xD0730000u, 46u}, // tdu -> Latn
-    {0x74650000u, 88u}, // te -> Telu
-    {0x8C930000u, 46u}, // ted -> Latn
-    {0xB0930000u, 46u}, // tem -> Latn
-    {0xB8930000u, 46u}, // teo -> Latn
-    {0xCC930000u, 46u}, // tet -> Latn
-    {0xA0B30000u, 46u}, // tfi -> Latn
+    {0xD0730000u, 45u}, // tdu -> Latn
+    {0x74650000u, 89u}, // te -> Telu
+    {0x8C930000u, 45u}, // ted -> Latn
+    {0xB0930000u, 45u}, // tem -> Latn
+    {0xB8930000u, 45u}, // teo -> Latn
+    {0xCC930000u, 45u}, // tet -> Latn
+    {0xA0B30000u, 45u}, // tfi -> Latn
     {0x74670000u, 18u}, // tg -> Cyrl
     {0x7467504Bu,  2u}, // tg-PK -> Arab
-    {0x88D30000u, 46u}, // tgc -> Latn
-    {0xB8D30000u, 46u}, // tgo -> Latn
-    {0xD0D30000u, 46u}, // tgu -> Latn
-    {0x74680000u, 91u}, // th -> Thai
+    {0x88D30000u, 45u}, // tgc -> Latn
+    {0xB8D30000u, 45u}, // tgo -> Latn
+    {0xD0D30000u, 45u}, // tgu -> Latn
+    {0x74680000u, 92u}, // th -> Thai
     {0xACF30000u, 19u}, // thl -> Deva
     {0xC0F30000u, 19u}, // thq -> Deva
     {0xC4F30000u, 19u}, // thr -> Deva
     {0x74690000u, 21u}, // ti -> Ethi
-    {0x95130000u, 46u}, // tif -> Latn
+    {0x95130000u, 45u}, // tif -> Latn
     {0x99130000u, 21u}, // tig -> Ethi
-    {0xA9130000u, 46u}, // tik -> Latn
-    {0xB1130000u, 46u}, // tim -> Latn
-    {0xB9130000u, 46u}, // tio -> Latn
-    {0xD5130000u, 46u}, // tiv -> Latn
-    {0x746B0000u, 46u}, // tk -> Latn
-    {0xAD530000u, 46u}, // tkl -> Latn
-    {0xC5530000u, 46u}, // tkr -> Latn
+    {0xA9130000u, 45u}, // tik -> Latn
+    {0xB1130000u, 45u}, // tim -> Latn
+    {0xB9130000u, 45u}, // tio -> Latn
+    {0xD5130000u, 45u}, // tiv -> Latn
+    {0x746B0000u, 45u}, // tk -> Latn
+    {0xAD530000u, 45u}, // tkl -> Latn
+    {0xC5530000u, 45u}, // tkr -> Latn
     {0xCD530000u, 19u}, // tkt -> Deva
-    {0x746C0000u, 46u}, // tl -> Latn
-    {0x95730000u, 46u}, // tlf -> Latn
-    {0xDD730000u, 46u}, // tlx -> Latn
-    {0xE1730000u, 46u}, // tly -> Latn
-    {0x9D930000u, 46u}, // tmh -> Latn
-    {0xE1930000u, 46u}, // tmy -> Latn
-    {0x746E0000u, 46u}, // tn -> Latn
-    {0x9DB30000u, 46u}, // tnh -> Latn
-    {0x746F0000u, 46u}, // to -> Latn
-    {0x95D30000u, 46u}, // tof -> Latn
-    {0x99D30000u, 46u}, // tog -> Latn
-    {0xC1D30000u, 46u}, // toq -> Latn
-    {0xA1F30000u, 46u}, // tpi -> Latn
-    {0xB1F30000u, 46u}, // tpm -> Latn
-    {0xE5F30000u, 46u}, // tpz -> Latn
-    {0xBA130000u, 46u}, // tqo -> Latn
-    {0x74720000u, 46u}, // tr -> Latn
-    {0xD2330000u, 46u}, // tru -> Latn
-    {0xD6330000u, 46u}, // trv -> Latn
+    {0x746C0000u, 45u}, // tl -> Latn
+    {0x95730000u, 45u}, // tlf -> Latn
+    {0xDD730000u, 45u}, // tlx -> Latn
+    {0xE1730000u, 45u}, // tly -> Latn
+    {0x9D930000u, 45u}, // tmh -> Latn
+    {0xE1930000u, 45u}, // tmy -> Latn
+    {0x746E0000u, 45u}, // tn -> Latn
+    {0x9DB30000u, 45u}, // tnh -> Latn
+    {0x746F0000u, 45u}, // to -> Latn
+    {0x95D30000u, 45u}, // tof -> Latn
+    {0x99D30000u, 45u}, // tog -> Latn
+    {0xC1D30000u, 45u}, // toq -> Latn
+    {0xA1F30000u, 45u}, // tpi -> Latn
+    {0xB1F30000u, 45u}, // tpm -> Latn
+    {0xE5F30000u, 45u}, // tpz -> Latn
+    {0xBA130000u, 45u}, // tqo -> Latn
+    {0x74720000u, 45u}, // tr -> Latn
+    {0xD2330000u, 45u}, // tru -> Latn
+    {0xD6330000u, 45u}, // trv -> Latn
     {0xDA330000u,  2u}, // trw -> Arab
-    {0x74730000u, 46u}, // ts -> Latn
+    {0x74730000u, 45u}, // ts -> Latn
     {0x8E530000u, 26u}, // tsd -> Grek
     {0x96530000u, 19u}, // tsf -> Deva
-    {0x9A530000u, 46u}, // tsg -> Latn
-    {0xA6530000u, 92u}, // tsj -> Tibt
-    {0xDA530000u, 46u}, // tsw -> Latn
+    {0x9A530000u, 45u}, // tsg -> Latn
+    {0xA6530000u, 93u}, // tsj -> Tibt
+    {0xDA530000u, 45u}, // tsw -> Latn
     {0x74740000u, 18u}, // tt -> Cyrl
-    {0x8E730000u, 46u}, // ttd -> Latn
-    {0x92730000u, 46u}, // tte -> Latn
-    {0xA6730000u, 46u}, // ttj -> Latn
-    {0xC6730000u, 46u}, // ttr -> Latn
-    {0xCA730000u, 91u}, // tts -> Thai
-    {0xCE730000u, 46u}, // ttt -> Latn
-    {0x9E930000u, 46u}, // tuh -> Latn
-    {0xAE930000u, 46u}, // tul -> Latn
-    {0xB2930000u, 46u}, // tum -> Latn
-    {0xC2930000u, 46u}, // tuq -> Latn
-    {0x8EB30000u, 46u}, // tvd -> Latn
-    {0xAEB30000u, 46u}, // tvl -> Latn
-    {0xD2B30000u, 46u}, // tvu -> Latn
-    {0x9ED30000u, 46u}, // twh -> Latn
-    {0xC2D30000u, 46u}, // twq -> Latn
-    {0x9AF30000u, 86u}, // txg -> Tang
-    {0x74790000u, 46u}, // ty -> Latn
-    {0x83130000u, 46u}, // tya -> Latn
+    {0x8E730000u, 45u}, // ttd -> Latn
+    {0x92730000u, 45u}, // tte -> Latn
+    {0xA6730000u, 45u}, // ttj -> Latn
+    {0xC6730000u, 45u}, // ttr -> Latn
+    {0xCA730000u, 92u}, // tts -> Thai
+    {0xCE730000u, 45u}, // ttt -> Latn
+    {0x9E930000u, 45u}, // tuh -> Latn
+    {0xAE930000u, 45u}, // tul -> Latn
+    {0xB2930000u, 45u}, // tum -> Latn
+    {0xC2930000u, 45u}, // tuq -> Latn
+    {0x8EB30000u, 45u}, // tvd -> Latn
+    {0xAEB30000u, 45u}, // tvl -> Latn
+    {0xD2B30000u, 45u}, // tvu -> Latn
+    {0x9ED30000u, 45u}, // twh -> Latn
+    {0xC2D30000u, 45u}, // twq -> Latn
+    {0x9AF30000u, 87u}, // txg -> Tang
+    {0xBAF30000u, 95u}, // txo -> Toto
+    {0x74790000u, 45u}, // ty -> Latn
+    {0x83130000u, 45u}, // tya -> Latn
     {0xD7130000u, 18u}, // tyv -> Cyrl
-    {0xB3330000u, 46u}, // tzm -> Latn
-    {0xD0340000u, 46u}, // ubu -> Latn
+    {0xB3330000u, 45u}, // tzm -> Latn
+    {0xD0340000u, 45u}, // ubu -> Latn
     {0xA0740000u,  0u}, // udi -> Aghb
     {0xB0740000u, 18u}, // udm -> Cyrl
     {0x75670000u,  2u}, // ug -> Arab
     {0x75674B5Au, 18u}, // ug-KZ -> Cyrl
     {0x75674D4Eu, 18u}, // ug-MN -> Cyrl
-    {0x80D40000u, 93u}, // uga -> Ugar
+    {0x80D40000u, 96u}, // uga -> Ugar
     {0x756B0000u, 18u}, // uk -> Cyrl
-    {0xA1740000u, 46u}, // uli -> Latn
-    {0x85940000u, 46u}, // umb -> Latn
+    {0xA1740000u, 45u}, // uli -> Latn
+    {0x85940000u, 45u}, // umb -> Latn
     {0xC5B40000u,  8u}, // unr -> Beng
     {0xC5B44E50u, 19u}, // unr-NP -> Deva
     {0xDDB40000u,  8u}, // unx -> Beng
-    {0xA9D40000u, 46u}, // uok -> Latn
+    {0xA9D40000u, 45u}, // uok -> Latn
     {0x75720000u,  2u}, // ur -> Arab
-    {0xA2340000u, 46u}, // uri -> Latn
-    {0xCE340000u, 46u}, // urt -> Latn
-    {0xDA340000u, 46u}, // urw -> Latn
-    {0x82540000u, 46u}, // usa -> Latn
-    {0x9E740000u, 46u}, // uth -> Latn
-    {0xC6740000u, 46u}, // utr -> Latn
-    {0x9EB40000u, 46u}, // uvh -> Latn
-    {0xAEB40000u, 46u}, // uvl -> Latn
-    {0x757A0000u, 46u}, // uz -> Latn
+    {0xA2340000u, 45u}, // uri -> Latn
+    {0xCE340000u, 45u}, // urt -> Latn
+    {0xDA340000u, 45u}, // urw -> Latn
+    {0x82540000u, 45u}, // usa -> Latn
+    {0x9E740000u, 45u}, // uth -> Latn
+    {0xC6740000u, 45u}, // utr -> Latn
+    {0x9EB40000u, 45u}, // uvh -> Latn
+    {0xAEB40000u, 45u}, // uvl -> Latn
+    {0x757A0000u, 45u}, // uz -> Latn
     {0x757A4146u,  2u}, // uz-AF -> Arab
     {0x757A434Eu, 18u}, // uz-CN -> Cyrl
-    {0x98150000u, 46u}, // vag -> Latn
-    {0xA0150000u, 94u}, // vai -> Vaii
-    {0xB4150000u, 46u}, // van -> Latn
-    {0x76650000u, 46u}, // ve -> Latn
-    {0x88950000u, 46u}, // vec -> Latn
-    {0xBC950000u, 46u}, // vep -> Latn
-    {0x76690000u, 46u}, // vi -> Latn
-    {0x89150000u, 46u}, // vic -> Latn
-    {0xD5150000u, 46u}, // viv -> Latn
-    {0xC9750000u, 46u}, // vls -> Latn
-    {0x95950000u, 46u}, // vmf -> Latn
-    {0xD9950000u, 46u}, // vmw -> Latn
-    {0x766F0000u, 46u}, // vo -> Latn
-    {0xCDD50000u, 46u}, // vot -> Latn
-    {0xBA350000u, 46u}, // vro -> Latn
-    {0xB6950000u, 46u}, // vun -> Latn
-    {0xCE950000u, 46u}, // vut -> Latn
-    {0x77610000u, 46u}, // wa -> Latn
-    {0x90160000u, 46u}, // wae -> Latn
-    {0xA4160000u, 46u}, // waj -> Latn
+    {0x98150000u, 45u}, // vag -> Latn
+    {0xA0150000u, 97u}, // vai -> Vaii
+    {0xB4150000u, 45u}, // van -> Latn
+    {0x76650000u, 45u}, // ve -> Latn
+    {0x88950000u, 45u}, // vec -> Latn
+    {0xBC950000u, 45u}, // vep -> Latn
+    {0x76690000u, 45u}, // vi -> Latn
+    {0x89150000u, 45u}, // vic -> Latn
+    {0xD5150000u, 45u}, // viv -> Latn
+    {0xC9750000u, 45u}, // vls -> Latn
+    {0x95950000u, 45u}, // vmf -> Latn
+    {0xD9950000u, 45u}, // vmw -> Latn
+    {0x766F0000u, 45u}, // vo -> Latn
+    {0xCDD50000u, 45u}, // vot -> Latn
+    {0xBA350000u, 45u}, // vro -> Latn
+    {0xB6950000u, 45u}, // vun -> Latn
+    {0xCE950000u, 45u}, // vut -> Latn
+    {0x77610000u, 45u}, // wa -> Latn
+    {0x90160000u, 45u}, // wae -> Latn
+    {0xA4160000u, 45u}, // waj -> Latn
     {0xAC160000u, 21u}, // wal -> Ethi
-    {0xB4160000u, 46u}, // wan -> Latn
-    {0xC4160000u, 46u}, // war -> Latn
-    {0xBC360000u, 46u}, // wbp -> Latn
-    {0xC0360000u, 88u}, // wbq -> Telu
+    {0xB4160000u, 45u}, // wan -> Latn
+    {0xC4160000u, 45u}, // war -> Latn
+    {0xBC360000u, 45u}, // wbp -> Latn
+    {0xC0360000u, 89u}, // wbq -> Telu
     {0xC4360000u, 19u}, // wbr -> Deva
-    {0xA0560000u, 46u}, // wci -> Latn
-    {0xC4960000u, 46u}, // wer -> Latn
-    {0xA0D60000u, 46u}, // wgi -> Latn
-    {0x98F60000u, 46u}, // whg -> Latn
-    {0x85160000u, 46u}, // wib -> Latn
-    {0xD1160000u, 46u}, // wiu -> Latn
-    {0xD5160000u, 46u}, // wiv -> Latn
-    {0x81360000u, 46u}, // wja -> Latn
-    {0xA1360000u, 46u}, // wji -> Latn
-    {0xC9760000u, 46u}, // wls -> Latn
-    {0xB9960000u, 46u}, // wmo -> Latn
-    {0x89B60000u, 46u}, // wnc -> Latn
+    {0xA0560000u, 45u}, // wci -> Latn
+    {0xC4960000u, 45u}, // wer -> Latn
+    {0xA0D60000u, 45u}, // wgi -> Latn
+    {0x98F60000u, 45u}, // whg -> Latn
+    {0x85160000u, 45u}, // wib -> Latn
+    {0xD1160000u, 45u}, // wiu -> Latn
+    {0xD5160000u, 45u}, // wiv -> Latn
+    {0x81360000u, 45u}, // wja -> Latn
+    {0xA1360000u, 45u}, // wji -> Latn
+    {0xC9760000u, 45u}, // wls -> Latn
+    {0xB9960000u, 45u}, // wmo -> Latn
+    {0x89B60000u, 45u}, // wnc -> Latn
     {0xA1B60000u,  2u}, // wni -> Arab
-    {0xD1B60000u, 46u}, // wnu -> Latn
-    {0x776F0000u, 46u}, // wo -> Latn
-    {0x85D60000u, 46u}, // wob -> Latn
-    {0xC9D60000u, 46u}, // wos -> Latn
-    {0xCA360000u, 46u}, // wrs -> Latn
+    {0xD1B60000u, 45u}, // wnu -> Latn
+    {0x776F0000u, 45u}, // wo -> Latn
+    {0x85D60000u, 45u}, // wob -> Latn
+    {0xC9D60000u, 45u}, // wos -> Latn
+    {0xCA360000u, 45u}, // wrs -> Latn
     {0x9A560000u, 23u}, // wsg -> Gong
-    {0xAA560000u, 46u}, // wsk -> Latn
+    {0xAA560000u, 45u}, // wsk -> Latn
     {0xB2760000u, 19u}, // wtm -> Deva
     {0xD2960000u, 29u}, // wuu -> Hans
-    {0xD6960000u, 46u}, // wuv -> Latn
-    {0x82D60000u, 46u}, // wwa -> Latn
-    {0xD4170000u, 46u}, // xav -> Latn
-    {0xA0370000u, 46u}, // xbi -> Latn
+    {0xD6960000u, 45u}, // wuv -> Latn
+    {0x82D60000u, 45u}, // wwa -> Latn
+    {0xD4170000u, 45u}, // xav -> Latn
+    {0xA0370000u, 45u}, // xbi -> Latn
     {0xB8570000u, 15u}, // xco -> Chrs
     {0xC4570000u, 12u}, // xcr -> Cari
-    {0xC8970000u, 46u}, // xes -> Latn
-    {0x78680000u, 46u}, // xh -> Latn
-    {0x81770000u, 46u}, // xla -> Latn
-    {0x89770000u, 50u}, // xlc -> Lyci
-    {0x8D770000u, 51u}, // xld -> Lydi
+    {0xC8970000u, 45u}, // xes -> Latn
+    {0x78680000u, 45u}, // xh -> Latn
+    {0x81770000u, 45u}, // xla -> Latn
+    {0x89770000u, 49u}, // xlc -> Lyci
+    {0x8D770000u, 50u}, // xld -> Lydi
     {0x95970000u, 22u}, // xmf -> Geor
-    {0xB5970000u, 53u}, // xmn -> Mani
-    {0xC5970000u, 55u}, // xmr -> Merc
-    {0x81B70000u, 60u}, // xna -> Narb
+    {0xB5970000u, 52u}, // xmn -> Mani
+    {0xC5970000u, 54u}, // xmr -> Merc
+    {0x81B70000u, 59u}, // xna -> Narb
     {0xC5B70000u, 19u}, // xnr -> Deva
-    {0x99D70000u, 46u}, // xog -> Latn
-    {0xB5D70000u, 46u}, // xon -> Latn
+    {0x99D70000u, 45u}, // xog -> Latn
+    {0xB5D70000u, 45u}, // xon -> Latn
     {0xC5F70000u, 72u}, // xpr -> Prti
-    {0x86370000u, 46u}, // xrb -> Latn
-    {0x82570000u, 75u}, // xsa -> Sarb
-    {0xA2570000u, 46u}, // xsi -> Latn
-    {0xB2570000u, 46u}, // xsm -> Latn
+    {0x86370000u, 45u}, // xrb -> Latn
+    {0x82570000u, 76u}, // xsa -> Sarb
+    {0xA2570000u, 45u}, // xsi -> Latn
+    {0xB2570000u, 45u}, // xsm -> Latn
     {0xC6570000u, 19u}, // xsr -> Deva
-    {0x92D70000u, 46u}, // xwe -> Latn
-    {0xB0180000u, 46u}, // yam -> Latn
-    {0xB8180000u, 46u}, // yao -> Latn
-    {0xBC180000u, 46u}, // yap -> Latn
-    {0xC8180000u, 46u}, // yas -> Latn
-    {0xCC180000u, 46u}, // yat -> Latn
-    {0xD4180000u, 46u}, // yav -> Latn
-    {0xE0180000u, 46u}, // yay -> Latn
-    {0xE4180000u, 46u}, // yaz -> Latn
-    {0x80380000u, 46u}, // yba -> Latn
-    {0x84380000u, 46u}, // ybb -> Latn
-    {0xE0380000u, 46u}, // yby -> Latn
-    {0xC4980000u, 46u}, // yer -> Latn
-    {0xC4D80000u, 46u}, // ygr -> Latn
-    {0xD8D80000u, 46u}, // ygw -> Latn
+    {0x92D70000u, 45u}, // xwe -> Latn
+    {0xB0180000u, 45u}, // yam -> Latn
+    {0xB8180000u, 45u}, // yao -> Latn
+    {0xBC180000u, 45u}, // yap -> Latn
+    {0xC8180000u, 45u}, // yas -> Latn
+    {0xCC180000u, 45u}, // yat -> Latn
+    {0xD4180000u, 45u}, // yav -> Latn
+    {0xE0180000u, 45u}, // yay -> Latn
+    {0xE4180000u, 45u}, // yaz -> Latn
+    {0x80380000u, 45u}, // yba -> Latn
+    {0x84380000u, 45u}, // ybb -> Latn
+    {0xE0380000u, 45u}, // yby -> Latn
+    {0xC4980000u, 45u}, // yer -> Latn
+    {0xC4D80000u, 45u}, // ygr -> Latn
+    {0xD8D80000u, 45u}, // ygw -> Latn
     {0x79690000u, 31u}, // yi -> Hebr
-    {0xB9580000u, 46u}, // yko -> Latn
-    {0x91780000u, 46u}, // yle -> Latn
-    {0x99780000u, 46u}, // ylg -> Latn
-    {0xAD780000u, 46u}, // yll -> Latn
-    {0xAD980000u, 46u}, // yml -> Latn
-    {0x796F0000u, 46u}, // yo -> Latn
-    {0xB5D80000u, 46u}, // yon -> Latn
-    {0x86380000u, 46u}, // yrb -> Latn
-    {0x92380000u, 46u}, // yre -> Latn
-    {0xAE380000u, 46u}, // yrl -> Latn
-    {0xCA580000u, 46u}, // yss -> Latn
-    {0x82980000u, 46u}, // yua -> Latn
+    {0xB9580000u, 45u}, // yko -> Latn
+    {0x91780000u, 45u}, // yle -> Latn
+    {0x99780000u, 45u}, // ylg -> Latn
+    {0xAD780000u, 45u}, // yll -> Latn
+    {0xAD980000u, 45u}, // yml -> Latn
+    {0x796F0000u, 45u}, // yo -> Latn
+    {0xB5D80000u, 45u}, // yon -> Latn
+    {0x86380000u, 45u}, // yrb -> Latn
+    {0x92380000u, 45u}, // yre -> Latn
+    {0xAE380000u, 45u}, // yrl -> Latn
+    {0xCA580000u, 45u}, // yss -> Latn
+    {0x82980000u, 45u}, // yua -> Latn
     {0x92980000u, 30u}, // yue -> Hant
     {0x9298434Eu, 29u}, // yue-CN -> Hans
-    {0xA6980000u, 46u}, // yuj -> Latn
-    {0xCE980000u, 46u}, // yut -> Latn
-    {0xDA980000u, 46u}, // yuw -> Latn
-    {0x7A610000u, 46u}, // za -> Latn
-    {0x98190000u, 46u}, // zag -> Latn
+    {0xA6980000u, 45u}, // yuj -> Latn
+    {0xCE980000u, 45u}, // yut -> Latn
+    {0xDA980000u, 45u}, // yuw -> Latn
+    {0x7A610000u, 45u}, // za -> Latn
+    {0x98190000u, 45u}, // zag -> Latn
     {0xA4790000u,  2u}, // zdj -> Arab
-    {0x80990000u, 46u}, // zea -> Latn
-    {0x9CD90000u, 89u}, // zgh -> Tfng
+    {0x80990000u, 45u}, // zea -> Latn
+    {0x9CD90000u, 90u}, // zgh -> Tfng
     {0x7A680000u, 29u}, // zh -> Hans
     {0x7A684155u, 30u}, // zh-AU -> Hant
     {0x7A68424Eu, 30u}, // zh-BN -> Hant
@@ -1486,14 +1493,14 @@
     {0x7A685457u, 30u}, // zh-TW -> Hant
     {0x7A685553u, 30u}, // zh-US -> Hant
     {0x7A68564Eu, 30u}, // zh-VN -> Hant
-    {0xDCF90000u, 62u}, // zhx -> Nshu
-    {0x81190000u, 46u}, // zia -> Latn
-    {0xCD590000u, 41u}, // zkt -> Kits
-    {0xB1790000u, 46u}, // zlm -> Latn
-    {0xA1990000u, 46u}, // zmi -> Latn
-    {0x91B90000u, 46u}, // zne -> Latn
-    {0x7A750000u, 46u}, // zu -> Latn
-    {0x83390000u, 46u}, // zza -> Latn
+    {0xDCF90000u, 61u}, // zhx -> Nshu
+    {0x81190000u, 45u}, // zia -> Latn
+    {0xCD590000u, 40u}, // zkt -> Kits
+    {0xB1790000u, 45u}, // zlm -> Latn
+    {0xA1990000u, 45u}, // zmi -> Latn
+    {0x91B90000u, 45u}, // zne -> Latn
+    {0x7A750000u, 45u}, // zu -> Latn
+    {0x83390000u, 45u}, // zza -> Latn
 });
 
 std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -1573,6 +1580,7 @@
     0xCD21534E4C61746ELLU, // bjt_Latn_SN
     0xB141434D4C61746ELLU, // bkm_Latn_CM
     0xD14150484C61746ELLU, // bku_Latn_PH
+    0x99614D594C61746ELLU, // blg_Latn_MY
     0xCD61564E54617674LLU, // blt_Tavt_VN
     0x626D4D4C4C61746ELLU, // bm_Latn_ML
     0xC1814D4C4C61746ELLU, // bmq_Latn_ML
@@ -1748,7 +1756,7 @@
     0x8D87434E506C7264LLU, // hmd_Plrd_CN
     0x8DA7504B41726162LLU, // hnd_Arab_PK
     0x91A7494E44657661LLU, // hne_Deva_IN
-    0xA5A74C41486D6E67LLU, // hnj_Hmng_LA
+    0xA5A75553486D6E70LLU, // hnj_Hmnp_US
     0xB5A750484C61746ELLU, // hnn_Latn_PH
     0xB9A7504B41726162LLU, // hno_Arab_PK
     0x686F50474C61746ELLU, // ho_Latn_PG
@@ -1797,7 +1805,7 @@
     0x984A4E474C61746ELLU, // kcg_Latn_NG
     0xA84A5A574C61746ELLU, // kck_Latn_ZW
     0x906A545A4C61746ELLU, // kde_Latn_TZ
-    0x9C6A544741726162LLU, // kdh_Arab_TG
+    0x9C6A54474C61746ELLU, // kdh_Latn_TG
     0xCC6A544854686169LLU, // kdt_Thai_TH
     0x808A43564C61746ELLU, // kea_Latn_CV
     0xB48A434D4C61746ELLU, // ken_Latn_CM
@@ -1982,6 +1990,7 @@
     0x6E725A414C61746ELLU, // nr_Latn_ZA
     0xAA4D434143616E73LLU, // nsk_Cans_CA
     0xBA4D5A414C61746ELLU, // nso_Latn_ZA
+    0xCE4D494E546E7361LLU, // nst_Tnsa_IN
     0xCA8D53534C61746ELLU, // nus_Latn_SS
     0x6E7655534C61746ELLU, // nv_Latn_US
     0xC2ED434E4C61746ELLU, // nxq_Latn_CN
@@ -1995,6 +2004,7 @@
     0x6F7347454379726CLLU, // os_Cyrl_GE
     0x824E55534F736765LLU, // osa_Osge_US
     0xAA6E4D4E4F726B68LLU, // otk_Orkh_MN
+    0xA28E8C814F756772LLU, // oui_Ougr_143
     0x7061504B41726162LLU, // pa_Arab_PK
     0x7061494E47757275LLU, // pa_Guru_IN
     0x980F50484C61746ELLU, // pag_Latn_PH
@@ -2029,7 +2039,7 @@
     0x945152454C61746ELLU, // rcf_Latn_RE
     0xA49149444C61746ELLU, // rej_Latn_ID
     0xB4D149544C61746ELLU, // rgn_Latn_IT
-    0x98F14D4D41726162LLU, // rhg_Arab_MM
+    0x98F14D4D526F6867LLU, // rhg_Rohg_MM
     0x8111494E4C61746ELLU, // ria_Latn_IN
     0x95114D4154666E67LLU, // rif_Tfng_MA
     0xC9314E5044657661LLU, // rjs_Deva_NP
@@ -2172,6 +2182,7 @@
     0xAEB354564C61746ELLU, // tvl_Latn_TV
     0xC2D34E454C61746ELLU, // twq_Latn_NE
     0x9AF3434E54616E67LLU, // txg_Tang_CN
+    0xBAF3494E546F746FLLU, // txo_Toto_IN
     0x747950464C61746ELLU, // ty_Latn_PF
     0xD71352554379726CLLU, // tyv_Cyrl_RU
     0xB3334D414C61746ELLU, // tzm_Latn_MA
@@ -2256,6 +2267,7 @@
 });
 
 const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({
+    {0x61724145u, 0x61729420u}, // ar-AE -> ar-015
     {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015
     {0x61724548u, 0x61729420u}, // ar-EH -> ar-015
     {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015
@@ -2279,7 +2291,6 @@
     {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
     {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
     {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001
-    {0x656E4341u, 0x656E8400u}, // en-CA -> en-001
     {0x656E4343u, 0x656E8400u}, // en-CC -> en-001
     {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150
     {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001
@@ -2332,7 +2343,6 @@
     {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001
     {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001
     {0x656E5047u, 0x656E8400u}, // en-PG -> en-001
-    {0x656E5048u, 0x656E8400u}, // en-PH -> en-001
     {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001
     {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001
     {0x656E5057u, 0x656E8400u}, // en-PW -> en-001
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 4d8eda1..c439356 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -23,19 +23,35 @@
 
 /** @hide */
 public abstract class BroadcastInfoRequest implements Parcelable {
-    protected static final int PARCEL_TOKEN_TS_REQUEST = 1;
+
+    // todo: change const declaration to intdef
+    public static final int REQUEST_OPTION_REPEAT = 11;
+    public static final int REQUEST_OPTION_AUTO_UPDATE = 12;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
             new Parcelable.Creator<BroadcastInfoRequest>() {
                 @Override
                 public BroadcastInfoRequest createFromParcel(Parcel source) {
-                    int token = source.readInt();
-                    switch (token) {
-                        case PARCEL_TOKEN_TS_REQUEST:
+                    int type = source.readInt();
+                    switch (type) {
+                        case BroadcastInfoType.TS:
                             return TsRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.TABLE:
+                            return TableRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.SECTION:
+                            return SectionRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.PES:
+                            return PesRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.STREAM_EVENT:
+                            return StreamEventRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.DSMCC:
+                            return DsmccRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+                            return TvProprietaryFunctionRequest.createFromParcelBody(source);
                         default:
                             throw new IllegalStateException(
-                                    "Unexpected broadcast info request type token in parcel.");
+                                    "Unexpected broadcast info request type (value "
+                                            + type + ") in parcel.");
                     }
                 }
 
@@ -45,18 +61,32 @@
                 }
             };
 
-    int requestId;
+    protected final int mType;
+    protected final int mRequestId;
+    protected final int mOption;
 
-    public BroadcastInfoRequest(int requestId) {
-        this.requestId = requestId;
+    protected BroadcastInfoRequest(int type, int requestId, int option) {
+        mType = type;
+        mRequestId = requestId;
+        mOption = option;
     }
 
-    protected BroadcastInfoRequest(Parcel source) {
-        requestId = source.readInt();
+    protected BroadcastInfoRequest(int type, Parcel source) {
+        mType = type;
+        mRequestId = source.readInt();
+        mOption = source.readInt();
+    }
+
+    public int getType() {
+        return mType;
     }
 
     public int getRequestId() {
-        return requestId;
+        return mRequestId;
+    }
+
+    public int getOption() {
+        return mOption;
     }
 
     @Override
@@ -66,6 +96,8 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(requestId);
+        dest.writeInt(mType);
+        dest.writeInt(mRequestId);
+        dest.writeInt(mOption);
     }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index fe4e8b7..288f2f9 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -22,12 +22,37 @@
 import android.annotation.NonNull;
 
 /** @hide */
-public final class BroadcastInfoResponse implements Parcelable {
+public abstract class BroadcastInfoResponse implements Parcelable {
+    // todo: change const declaration to intdef
+    public static final int ERROR = 1;
+    public static final int OK = 2;
+    public static final int CANCEL = 3;
+
     public static final @NonNull Parcelable.Creator<BroadcastInfoResponse> CREATOR =
             new Parcelable.Creator<BroadcastInfoResponse>() {
                 @Override
                 public BroadcastInfoResponse createFromParcel(Parcel source) {
-                    return new BroadcastInfoResponse(source);
+                    int type = source.readInt();
+                    switch (type) {
+                        case BroadcastInfoType.TS:
+                            return TsResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.TABLE:
+                            return TableResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.SECTION:
+                            return SectionResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.PES:
+                            return PesResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.STREAM_EVENT:
+                            return StreamEventResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.DSMCC:
+                            return DsmccResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+                            return TvProprietaryFunctionResponse.createFromParcelBody(source);
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected broadcast info response type (value "
+                                            + type + ") in parcel.");
+                    }
                 }
 
                 @Override
@@ -36,18 +61,39 @@
                 }
             };
 
-    int requestId;
+    protected final int mType;
+    protected final int mRequestId;
+    protected final int mSequence;
+    protected final int mResponseResult;
 
-    public BroadcastInfoResponse(int requestId) {
-        this.requestId = requestId;
+    protected BroadcastInfoResponse(int type, int requestId, int sequence, int responseResult) {
+        mType = type;
+        mRequestId = requestId;
+        mSequence = sequence;
+        mResponseResult = responseResult;
     }
 
-    private BroadcastInfoResponse(Parcel source) {
-        requestId = source.readInt();
+    protected BroadcastInfoResponse(int type, Parcel source) {
+        mType = type;
+        mRequestId = source.readInt();
+        mSequence = source.readInt();
+        mResponseResult = source.readInt();
+    }
+
+    public int getType() {
+        return mType;
     }
 
     public int getRequestId() {
-        return requestId;
+        return mRequestId;
+    }
+
+    public int getSequence() {
+        return mSequence;
+    }
+
+    public int getResponseResult() {
+        return mResponseResult;
     }
 
     @Override
@@ -57,6 +103,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(requestId);
+        dest.writeInt(mType);
+        dest.writeInt(mRequestId);
+        dest.writeInt(mSequence);
+        dest.writeInt(mResponseResult);
     }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoType.java b/media/java/android/media/tv/BroadcastInfoType.java
new file mode 100644
index 0000000..e7a0595
--- /dev/null
+++ b/media/java/android/media/tv/BroadcastInfoType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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;
+
+/** @hide */
+public final class BroadcastInfoType {
+    // todo: change const declaration to intdef in TvInputManager
+    public static final int TS = 1;
+    public static final int TABLE = 2;
+    public static final int SECTION = 3;
+    public static final int PES = 4;
+    public static final int STREAM_EVENT = 5;
+    public static final int DSMCC = 6;
+    public static final int TV_PROPRIETARY_FUNCTION = 7;
+
+    private BroadcastInfoType() {
+    }
+}
diff --git a/media/java/android/media/tv/DsmccRequest.java b/media/java/android/media/tv/DsmccRequest.java
new file mode 100644
index 0000000..f2e4750
--- /dev/null
+++ b/media/java/android/media/tv/DsmccRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.DSMCC;
+
+    public static final @NonNull Parcelable.Creator<DsmccRequest> CREATOR =
+            new Parcelable.Creator<DsmccRequest>() {
+                @Override
+                public DsmccRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public DsmccRequest[] newArray(int size) {
+                    return new DsmccRequest[size];
+                }
+            };
+
+    private final Uri mUri;
+
+    public static DsmccRequest createFromParcelBody(Parcel in) {
+        return new DsmccRequest(in);
+    }
+
+    public DsmccRequest(int requestId, int option, Uri uri) {
+        super(requestType, requestId, option);
+        mUri = uri;
+    }
+
+    protected DsmccRequest(Parcel source) {
+        super(requestType, source);
+        String uriString = source.readString();
+        mUri = uriString == null ? null : Uri.parse(uriString);
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mUri == null ? null : mUri.toString();
+        dest.writeString(uriString);
+    }
+}
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
new file mode 100644
index 0000000..3bdfb95
--- /dev/null
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.DSMCC;
+
+    public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR =
+            new Parcelable.Creator<DsmccResponse>() {
+                @Override
+                public DsmccResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public DsmccResponse[] newArray(int size) {
+                    return new DsmccResponse[size];
+                }
+            };
+
+    private final ParcelFileDescriptor mFile;
+
+    public static DsmccResponse createFromParcelBody(Parcel in) {
+        return new DsmccResponse(in);
+    }
+
+    public DsmccResponse(int requestId, int sequence, int responseResult,
+            ParcelFileDescriptor file) {
+        super(responseType, requestId, sequence, responseResult);
+        mFile = file;
+    }
+
+    protected DsmccResponse(Parcel source) {
+        super(responseType, source);
+        mFile = source.readFileDescriptor();
+    }
+
+    public ParcelFileDescriptor getFile() {
+        return mFile;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        mFile.writeToParcel(dest, flags);
+    }
+}
diff --git a/media/java/android/media/tv/PesRequest.java b/media/java/android/media/tv/PesRequest.java
new file mode 100644
index 0000000..0e444b8
--- /dev/null
+++ b/media/java/android/media/tv/PesRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.PES;
+
+    public static final @NonNull Parcelable.Creator<PesRequest> CREATOR =
+            new Parcelable.Creator<PesRequest>() {
+                @Override
+                public PesRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public PesRequest[] newArray(int size) {
+                    return new PesRequest[size];
+                }
+            };
+
+    private final int mTsPid;
+    private final int mStreamId;
+
+    public static PesRequest createFromParcelBody(Parcel in) {
+        return new PesRequest(in);
+    }
+
+    public PesRequest(int requestId, int option, int tsPid, int streamId) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
+        mStreamId = streamId;
+    }
+
+    protected PesRequest(Parcel source) {
+        super(requestType, source);
+        mTsPid = source.readInt();
+        mStreamId = source.readInt();
+    }
+
+    public int getTsPid() {
+        return mTsPid;
+    }
+
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTsPid);
+        dest.writeInt(mStreamId);
+    }
+}
diff --git a/media/java/android/media/tv/PesResponse.java b/media/java/android/media/tv/PesResponse.java
new file mode 100644
index 0000000..d46e6fc
--- /dev/null
+++ b/media/java/android/media/tv/PesResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.PES;
+
+    public static final @NonNull Parcelable.Creator<PesResponse> CREATOR =
+            new Parcelable.Creator<PesResponse>() {
+                @Override
+                public PesResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public PesResponse[] newArray(int size) {
+                    return new PesResponse[size];
+                }
+            };
+
+    private final String mSharedFilterToken;
+
+    public static PesResponse createFromParcelBody(Parcel in) {
+        return new PesResponse(in);
+    }
+
+    public PesResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+        super(responseType, requestId, sequence, responseResult);
+        mSharedFilterToken = sharedFilterToken;
+    }
+
+    protected PesResponse(Parcel source) {
+        super(responseType, source);
+        mSharedFilterToken = source.readString();
+    }
+
+    public String getSharedFilterToken() {
+        return mSharedFilterToken;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mSharedFilterToken);
+    }
+}
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
new file mode 100644
index 0000000..3e8e909
--- /dev/null
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.SECTION;
+
+    public static final @NonNull Parcelable.Creator<SectionRequest> CREATOR =
+            new Parcelable.Creator<SectionRequest>() {
+                @Override
+                public SectionRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public SectionRequest[] newArray(int size) {
+                    return new SectionRequest[size];
+                }
+            };
+
+    private final int mTsPid;
+    private final int mTableId;
+    private final int mVersion;
+
+    public static SectionRequest createFromParcelBody(Parcel in) {
+        return new SectionRequest(in);
+    }
+
+    public SectionRequest(int requestId, int option, int tsPid, int tableId, int version) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
+        mTableId = tableId;
+        mVersion = version;
+    }
+
+    protected SectionRequest(Parcel source) {
+        super(requestType, source);
+        mTsPid = source.readInt();
+        mTableId = source.readInt();
+        mVersion = source.readInt();
+    }
+
+    public int getTsPid() {
+        return mTsPid;
+    }
+
+    public int getTableId() {
+        return mTableId;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTsPid);
+        dest.writeInt(mTableId);
+        dest.writeInt(mVersion);
+    }
+}
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
new file mode 100644
index 0000000..1c8f965
--- /dev/null
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.SECTION;
+
+    public static final @NonNull Parcelable.Creator<SectionResponse> CREATOR =
+            new Parcelable.Creator<SectionResponse>() {
+                @Override
+                public SectionResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public SectionResponse[] newArray(int size) {
+                    return new SectionResponse[size];
+                }
+            };
+
+    private final int mSessionId;
+    private final int mVersion;
+    private final Bundle mSessionData;
+
+    public static SectionResponse createFromParcelBody(Parcel in) {
+        return new SectionResponse(in);
+    }
+
+    public SectionResponse(int requestId, int sequence, int responseResult, int sessionId,
+            int version, Bundle sessionData) {
+        super(responseType, requestId, sequence, responseResult);
+        mSessionId = sessionId;
+        mVersion = version;
+        mSessionData = sessionData;
+    }
+
+    protected SectionResponse(Parcel source) {
+        super(responseType, source);
+        mSessionId = source.readInt();
+        mVersion = source.readInt();
+        mSessionData = source.readBundle();
+    }
+
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public Bundle getSessionData() {
+        return mSessionData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mSessionId);
+        dest.writeInt(mVersion);
+        dest.writeBundle(mSessionData);
+    }
+}
diff --git a/media/java/android/media/tv/StreamEventRequest.java b/media/java/android/media/tv/StreamEventRequest.java
new file mode 100644
index 0000000..09399c2
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.STREAM_EVENT;
+
+    public static final @NonNull Parcelable.Creator<StreamEventRequest> CREATOR =
+            new Parcelable.Creator<StreamEventRequest>() {
+                @Override
+                public StreamEventRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public StreamEventRequest[] newArray(int size) {
+                    return new StreamEventRequest[size];
+                }
+            };
+
+    private final Uri mTargetUri;
+    private final String mEventName;
+
+    public static StreamEventRequest createFromParcelBody(Parcel in) {
+        return new StreamEventRequest(in);
+    }
+
+    public StreamEventRequest(int requestId, int option, Uri targetUri, String eventName) {
+        super(requestType, requestId, option);
+        this.mTargetUri = targetUri;
+        this.mEventName = eventName;
+    }
+
+    protected StreamEventRequest(Parcel source) {
+        super(requestType, source);
+        String uriString = source.readString();
+        mTargetUri = uriString == null ? null : Uri.parse(uriString);
+        mEventName = source.readString();
+    }
+
+    public Uri getTargetUri() {
+        return mTargetUri;
+    }
+
+    public String getEventName() {
+        return mEventName;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mTargetUri == null ? null : mTargetUri.toString();
+        dest.writeString(uriString);
+        dest.writeString(mEventName);
+    }
+}
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
new file mode 100644
index 0000000..027b735
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.STREAM_EVENT;
+
+    public static final @NonNull Parcelable.Creator<StreamEventResponse> CREATOR =
+            new Parcelable.Creator<StreamEventResponse>() {
+                @Override
+                public StreamEventResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public StreamEventResponse[] newArray(int size) {
+                    return new StreamEventResponse[size];
+                }
+            };
+
+    private final String mName;
+    private final String mText;
+    private final String mData;
+    private final String mStatus;
+
+    public static StreamEventResponse createFromParcelBody(Parcel in) {
+        return new StreamEventResponse(in);
+    }
+
+    public StreamEventResponse(int requestId, int sequence, int responseResult, String name,
+            String text, String data, String status) {
+        super(responseType, requestId, sequence, responseResult);
+        mName = name;
+        mText = text;
+        mData = data;
+        mStatus = status;
+    }
+
+    protected StreamEventResponse(Parcel source) {
+        super(responseType, source);
+        mName = source.readString();
+        mText = source.readString();
+        mData = source.readString();
+        mStatus = source.readString();
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public String getData() {
+        return mData;
+    }
+
+    public String getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mName);
+        dest.writeString(mText);
+        dest.writeString(mData);
+        dest.writeString(mStatus);
+    }
+}
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
new file mode 100644
index 0000000..5432215
--- /dev/null
+++ b/media/java/android/media/tv/TableRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TableRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TABLE;
+
+    // todo: change const declaration to intdef
+    public static final int PAT = 1;
+    public static final int PMT = 2;
+
+    public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
+            new Parcelable.Creator<TableRequest>() {
+                @Override
+                public TableRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TableRequest[] newArray(int size) {
+                    return new TableRequest[size];
+                }
+            };
+
+    private final int mTableId;
+    private final int mTableName;
+    private final int mVersion;
+
+    public static TableRequest createFromParcelBody(Parcel in) {
+        return new TableRequest(in);
+    }
+
+    public TableRequest(int requestId, int option, int tableId, int tableName, int version) {
+        super(requestType, requestId, option);
+        mTableId = tableId;
+        mTableName = tableName;
+        mVersion = version;
+    }
+
+    protected TableRequest(Parcel source) {
+        super(requestType, source);
+        mTableId = source.readInt();
+        mTableName = source.readInt();
+        mVersion = source.readInt();
+    }
+
+    public int getTableId() {
+        return mTableId;
+    }
+
+    public int getTableName() {
+        return mTableName;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTableId);
+        dest.writeInt(mTableName);
+        dest.writeInt(mVersion);
+    }
+}
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
new file mode 100644
index 0000000..a6d3e39
--- /dev/null
+++ b/media/java/android/media/tv/TableResponse.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.net.Uri;
+
+/** @hide */
+public class TableResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TABLE;
+
+    public static final @NonNull Parcelable.Creator<TableResponse> CREATOR =
+            new Parcelable.Creator<TableResponse>() {
+                @Override
+                public TableResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TableResponse[] newArray(int size) {
+                    return new TableResponse[size];
+                }
+            };
+
+    private final Uri mTableUri;
+    private final int mVersion;
+    private final int mSize;
+
+    public static TableResponse createFromParcelBody(Parcel in) {
+        return new TableResponse(in);
+    }
+
+    public TableResponse(int requestId, int sequence, int responseResult, Uri tableUri,
+            int version, int size) {
+        super(responseType, requestId, sequence, responseResult);
+        mTableUri = tableUri;
+        mVersion = version;
+        mSize = size;
+    }
+
+    protected TableResponse(Parcel source) {
+        super(responseType, source);
+        String uriString = source.readString();
+        mTableUri = uriString == null ? null : Uri.parse(uriString);
+        mVersion = source.readInt();
+        mSize = source.readInt();
+    }
+
+    public Uri getTableUri() {
+        return mTableUri;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mTableUri == null ? null : mTableUri.toString();
+        dest.writeString(uriString);
+        dest.writeInt(mVersion);
+        dest.writeInt(mSize);
+    }
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/media/java/android/media/tv/TsRequest.aidl
deleted file mode 100644
index 0abc7ec..0000000
--- a/media/java/android/media/tv/TsRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 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;
-
-parcelable TsRequest;
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
index 3690d05..141f3ac 100644
--- a/media/java/android/media/tv/TsRequest.java
+++ b/media/java/android/media/tv/TsRequest.java
@@ -22,6 +22,8 @@
 
 /** @hide */
 public class TsRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TS;
+
     public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
             new Parcelable.Creator<TsRequest>() {
                 @Override
@@ -36,30 +38,29 @@
                 }
             };
 
-    int tsPid;
+    private final int mTsPid;
 
     public static TsRequest createFromParcelBody(Parcel in) {
         return new TsRequest(in);
     }
 
-    public TsRequest(int requestId, int tsPid) {
-        super(requestId);
-        this.tsPid = tsPid;
+    public TsRequest(int requestId, int option, int tsPid) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
     }
 
     protected TsRequest(Parcel source) {
-        super(source);
-        tsPid = source.readInt();
+        super(requestType, source);
+        mTsPid = source.readInt();
     }
 
     public int getTsPid() {
-        return tsPid;
+        return mTsPid;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(PARCEL_TOKEN_TS_REQUEST);
         super.writeToParcel(dest, flags);
-        dest.writeInt(tsPid);
+        dest.writeInt(mTsPid);
     }
 }
diff --git a/media/java/android/media/tv/TsResponse.java b/media/java/android/media/tv/TsResponse.java
new file mode 100644
index 0000000..e30ff54
--- /dev/null
+++ b/media/java/android/media/tv/TsResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TsResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TS;
+
+    public static final @NonNull Parcelable.Creator<TsResponse> CREATOR =
+            new Parcelable.Creator<TsResponse>() {
+                @Override
+                public TsResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TsResponse[] newArray(int size) {
+                    return new TsResponse[size];
+                }
+            };
+
+    private final String mSharedFilterToken;
+
+    public static TsResponse createFromParcelBody(Parcel in) {
+        return new TsResponse(in);
+    }
+
+    public TsResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+        super(responseType, requestId, sequence, responseResult);
+        this.mSharedFilterToken = sharedFilterToken;
+    }
+
+    protected TsResponse(Parcel source) {
+        super(responseType, source);
+        mSharedFilterToken = source.readString();
+    }
+
+    public String getSharedFilterToken() {
+        return mSharedFilterToken;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mSharedFilterToken);
+    }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionRequest.java b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
new file mode 100644
index 0000000..845641d
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+    public static final @NonNull Parcelable.Creator<TvProprietaryFunctionRequest> CREATOR =
+            new Parcelable.Creator<TvProprietaryFunctionRequest>() {
+                @Override
+                public TvProprietaryFunctionRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TvProprietaryFunctionRequest[] newArray(int size) {
+                    return new TvProprietaryFunctionRequest[size];
+                }
+            };
+
+    private final String mNameSpace;
+    private final String mName;
+    private final String mArguments;
+
+    public static TvProprietaryFunctionRequest createFromParcelBody(Parcel in) {
+        return new TvProprietaryFunctionRequest(in);
+    }
+
+    public TvProprietaryFunctionRequest(int requestId, int option, String nameSpace,
+            String name, String arguments) {
+        super(requestType, requestId, option);
+        mNameSpace = nameSpace;
+        mName = name;
+        mArguments = arguments;
+    }
+
+    protected TvProprietaryFunctionRequest(Parcel source) {
+        super(requestType, source);
+        mNameSpace = source.readString();
+        mName = source.readString();
+        mArguments = source.readString();
+    }
+
+    public String getNameSpace() {
+        return mNameSpace;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getArguments() {
+        return mArguments;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mNameSpace);
+        dest.writeString(mName);
+        dest.writeString(mArguments);
+    }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionResponse.java b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
new file mode 100644
index 0000000..3181b08
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+    public static final @NonNull Parcelable.Creator<TvProprietaryFunctionResponse> CREATOR =
+            new Parcelable.Creator<TvProprietaryFunctionResponse>() {
+                @Override
+                public TvProprietaryFunctionResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TvProprietaryFunctionResponse[] newArray(int size) {
+                    return new TvProprietaryFunctionResponse[size];
+                }
+            };
+
+    private final String mResponse;
+
+    public static TvProprietaryFunctionResponse createFromParcelBody(Parcel in) {
+        return new TvProprietaryFunctionResponse(in);
+    }
+
+    public TvProprietaryFunctionResponse(int requestId, int sequence, int responseResult,
+            String response) {
+        super(responseType, requestId, sequence, responseResult);
+        mResponse = response;
+    }
+
+    protected TvProprietaryFunctionResponse(Parcel source) {
+        super(responseType, source);
+        mResponse = source.readString();
+    }
+
+    public String getResponse() {
+        return mResponse;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mResponse);
+    }
+}
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
new file mode 100644
index 0000000..4862377
--- /dev/null
+++ b/packages/ConnectivityT/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index 1bc4983..1765348 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -8,6 +8,7 @@
     <uses-permission android:name="android.permission.REBOOT" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.READ_OEM_UNLOCK_STATE" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application
         android:allowBackup="false"
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ca3ec3c..688d116 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -112,6 +112,8 @@
     <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_application_text_user">Do you want to uninstall this app for the user <xliff:g id="username">%1$s</xliff:g>?</string>
     <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_current_user_work_profile">Do you want to uninstall this app from your work profile?</string>
+    <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_update_text">Replace this app with the factory version? All data will be removed.</string>
     <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 99f6a92..36294ac 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -125,6 +126,7 @@
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+        final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = UserManager.get(getActivity());
         if (isUpdate) {
             if (isSingleUser(userManager)) {
@@ -135,10 +137,17 @@
         } else {
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
-            } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
+            } else if (!dialogInfo.user.equals(myUserHandle)) {
                 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                messageBuilder.append(
-                        getString(R.string.uninstall_application_text_user, userInfo.name));
+                if (userInfo.isManagedProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_current_user_work_profile,
+                                    userInfo.name));
+                } else {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
             }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 21d25f5..2d241ca 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -21,7 +21,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
+
 import androidx.leanback.app.GuidedStepFragment;
 import androidx.leanback.widget.GuidanceStylist;
 import androidx.leanback.widget.GuidedAction;
@@ -59,6 +62,7 @@
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+        final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = UserManager.get(getActivity());
         if (isUpdate) {
             if (isSingleUser(userManager)) {
@@ -69,10 +73,17 @@
         } else {
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
-            } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
+            } else if (!dialogInfo.user.equals(myUserHandle)) {
                 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                messageBuilder.append(
-                        getString(R.string.uninstall_application_text_user, userInfo.name));
+                if (userInfo.isManagedProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_current_user_work_profile,
+                                    userInfo.name));
+                } else {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
             }
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index cb858c8..6d5615d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -109,7 +109,7 @@
             a.recycle();
         }
 
-        setBackground(true);
+        setBackground(mSwitch.isChecked());
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 8b17be1..dee6894 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -85,6 +85,7 @@
     @VisibleForTesting
     protected Context mContext;
     private final int mThemeResId;
+    private final boolean mCancelIsNeutral;
     @VisibleForTesting
     protected TextView mZenAlarmWarning;
     @VisibleForTesting
@@ -101,8 +102,13 @@
     }
 
     public EnableZenModeDialog(Context context, int themeResId) {
+        this(context, themeResId, false /* cancelIsNeutral */);
+    }
+
+    public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) {
         mContext = context;
         mThemeResId = themeResId;
+        mCancelIsNeutral = cancelIsNeutral;
     }
 
     public AlertDialog createDialog() {
@@ -115,7 +121,6 @@
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
                 .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
-                .setNegativeButton(R.string.cancel, null)
                 .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
                         new DialogInterface.OnClickListener() {
                             @Override
@@ -145,6 +150,12 @@
                             }
                         });
 
+        if (mCancelIsNeutral) {
+            builder.setNeutralButton(R.string.cancel, null);
+        } else {
+            builder.setNegativeButton(R.string.cancel, null);
+        }
+
         View contentView = getContentView();
         bindConditions(forever());
         builder.setView(contentView);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index dd1cb6b..e76e51d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -276,6 +276,8 @@
         VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f0f2c0f..fb8ffbd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -655,7 +655,8 @@
                     Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
                     Settings.Global.Wearable.WRIST_ORIENTATION_MODE,
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
-                    Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY);
+                    Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
+                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 262cf53..e5b5285 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -606,6 +606,10 @@
     <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
     <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
 
+    <!-- Permission required for CTS test - CommunalManagerTest -->
+    <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
+    <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bcf95d8..a1b3df8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -121,6 +121,7 @@
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
+    <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
     <uses-permission android:name="android.permission.INPUT_CONSUMER" />
 
     <!-- DreamManager -->
@@ -219,6 +220,7 @@
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <!-- It's like, reality, but, you know, virtual -->
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
deleted file mode 100644
index 50267fd..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="?androidprv:attr/colorAccentPrimaryVariant"
-        android:width="1dp"/>
-    <corners android:radius="20dp"/>
-    <padding
-        android:left="8dp"
-        android:right="8dp"
-        android:top="4dp"
-        android:bottom="4dp" />
-    <solid android:color="@android:color/transparent" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
deleted file mode 100644
index d9ae777..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:insetBottom="6dp"
-       android:insetTop="6dp">
-    <shape android:shape="rectangle">
-        <stroke
-            android:color="@color/media_dialog_outlined_button"
-            android:width="1dp"/>
-        <corners android:radius="20dp"/>
-        <padding
-            android:left="16dp"
-            android:right="16dp"
-            android:top="8dp"
-            android:bottom="8dp"/>
-        <solid android:color="@android:color/transparent"/>
-    </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
deleted file mode 100644
index d49454f..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:insetBottom="6dp"
-       android:insetTop="6dp">
-    <shape android:shape="rectangle">
-        <stroke
-            android:color="@android:color/transparent"
-            android:width="1dp"/>
-        <corners android:radius="20dp"/>
-        <padding
-            android:left="@dimen/media_output_dialog_button_padding_horizontal"
-            android:right="@dimen/media_output_dialog_button_padding_horizontal"
-            android:top="@dimen/media_output_dialog_button_padding_vertical"
-            android:bottom="@dimen/media_output_dialog_button_padding_vertical"/>
-        <solid android:color="@color/media_dialog_solid_button_background"/>
-    </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 665b456..a47299d 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/colorAccentPrimary"
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 46cbc25..1e0ce00 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,71 +18,64 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:elevation="@dimen/biometric_dialog_elevation"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:elevation="@dimen/biometric_dialog_elevation">
 
-    <RelativeLayout
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:id="@+id/title"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Title"/>
 
-        <LinearLayout
-            android:id="@+id/auth_credential_header"
-            style="@style/AuthCredentialHeaderStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true">
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Subtitle"/>
 
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:contentDescription="@null" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Description"/>
 
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
 
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+    <ImeAwareEditText
+        android:id="@+id/lockPassword"
+        android:layout_width="208dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:minHeight="48dp"
+        android:gravity="center"
+        android:inputType="textPassword"
+        android:maxLength="500"
+        android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+        style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
 
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+    <TextView
+        android:id="@+id/error"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Error"/>
 
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:layout_alignParentBottom="true">
-
-            <ImeAwareEditText
-                android:id="@+id/lockPassword"
-                style="@style/TextAppearance.AuthCredential.PasswordEntry"
-                android:layout_width="208dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
-                android:inputType="textPassword"
-                android:minHeight="48dp" />
-
-            <TextView
-                android:id="@+id/error"
-                style="@style/TextAppearance.AuthCredential.Error"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
-    </RelativeLayout>
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="5"/>
 
 </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 470298e..4939ea2 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,81 +22,76 @@
     android:gravity="center_horizontal"
     android:elevation="@dimen/biometric_dialog_elevation">
 
-    <RelativeLayout
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:id="@+id/title"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Title"/>
 
-        <LinearLayout
-            android:id="@+id/auth_credential_header"
-            style="@style/AuthCredentialHeaderStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Subtitle"/>
 
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:contentDescription="@null" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AuthCredential.Description"/>
 
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
 
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="center"
+        android:paddingLeft="0dp"
+        android:paddingRight="0dp"
+        android:paddingTop="0dp"
+        android:paddingBottom="16dp"
+        android:clipToPadding="false">
 
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-        </LinearLayout>
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            style="@style/LockPatternContainerStyle">
 
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/auth_credential_header"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:paddingBottom="16dp"
-            android:paddingTop="60dp">
-
-            <FrameLayout
-                style="@style/LockPatternContainerStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="0dp"
-                android:layout_weight="1">
-
-                <com.android.internal.widget.LockPatternView
-                    android:id="@+id/lockPattern"
-                    style="@style/LockPatternStyle"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_gravity="center" />
-
-            </FrameLayout>
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true">
-
-            <TextView
-                android:id="@+id/error"
-                style="@style/TextAppearance.AuthCredential.Error"
+            <com.android.internal.widget.LockPatternView
+                android:id="@+id/lockPattern"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                style="@style/LockPatternStyleBiometricPrompt"/>
 
-        </LinearLayout>
+        </FrameLayout>
 
-    </RelativeLayout>
+        <TextView
+            android:id="@+id/error"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/TextAppearance.AuthCredential.Error"/>
+
+    </LinearLayout>
+
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
 
 </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index c575855..275e0a5 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -37,7 +37,7 @@
             android:ellipsize="end"
             android:gravity="center_vertical|center_horizontal"
             android:layout_width="wrap_content"
-            android:layout_height="32dp"
+            android:layout_height="wrap_content"
             android:textAppearance="@style/TextAppearance.InternetDialog"
             android:textSize="24sp"/>
 
@@ -45,7 +45,7 @@
             android:id="@+id/internet_dialog_subtitle"
             android:gravity="center_vertical|center_horizontal"
             android:layout_width="wrap_content"
-            android:layout_height="20dp"
+            android:layout_height="wrap_content"
             android:layout_marginTop="4dp"
             android:ellipsize="end"
             android:maxLines="1"
@@ -150,6 +150,7 @@
                         android:gravity="start|center_vertical">
                         <TextView
                             android:id="@+id/mobile_title"
+                            android:maxLines="1"
                             style="@style/InternetDialog.NetworkTitle"/>
                         <TextView
                             android:id="@+id/mobile_summary"
@@ -380,54 +381,44 @@
                 android:id="@+id/button_layout"
                 android:orientation="horizontal"
                 android:layout_width="match_parent"
-                android:layout_height="48dp"
-                android:layout_marginStart="24dp"
-                android:layout_marginEnd="24dp"
+                android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
-                android:layout_marginBottom="34dp"
+                android:layout_marginStart="@dimen/dialog_side_padding"
+                android:layout_marginEnd="@dimen/dialog_side_padding"
+                android:layout_marginBottom="@dimen/dialog_bottom_padding"
                 android:clickable="false"
                 android:focusable="false">
 
                 <FrameLayout
                     android:id="@+id/apm_layout"
                     android:layout_width="wrap_content"
-                    android:layout_height="48dp"
+                    android:layout_height="wrap_content"
                     android:clickable="true"
                     android:focusable="true"
                     android:layout_gravity="start|center_vertical"
                     android:orientation="vertical">
                     <Button
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
                         android:text="@string/turn_off_airplane_mode"
                         android:ellipsize="end"
-                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                        android:layout_width="wrap_content"
-                        android:layout_height="36dp"
-                        android:layout_gravity="start|center_vertical"
-                        android:textAppearance="@style/TextAppearance.InternetDialog"
-                        android:textSize="14sp"
-                        android:background="@drawable/internet_dialog_footer_background"
+                        style="@style/Widget.Dialog.Button.BorderButton"
                         android:clickable="false"/>
                 </FrameLayout>
 
                 <FrameLayout
                     android:id="@+id/done_layout"
                     android:layout_width="wrap_content"
-                    android:layout_height="48dp"
+                    android:layout_height="wrap_content"
                     android:layout_marginStart="16dp"
-                    android:clickable="true"
-                    android:focusable="true"
                     android:layout_gravity="end|center_vertical"
-                    android:orientation="vertical">
+                    android:clickable="true"
+                    android:focusable="true">
                     <Button
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
                         android:text="@string/inline_done_button"
-                        android:ellipsize="end"
-                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                        android:layout_width="67dp"
-                        android:layout_height="36dp"
-                        android:layout_gravity="end|center_vertical"
-                        android:textAppearance="@style/TextAppearance.InternetDialog"
-                        android:textSize="14sp"
-                        android:background="@drawable/internet_dialog_footer_background"
+                        style="@style/Widget.Dialog.Button"
                         android:clickable="false"/>
                 </FrameLayout>
             </FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 8437702..b7265b9 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -41,7 +41,6 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingStart="12dp"
-            android:background="@color/media_dialog_background"
             android:orientation="vertical">
             <ImageView
                 android:id="@+id/app_source_icon"
@@ -76,7 +75,6 @@
         android:id="@+id/device_list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/media_dialog_background"
         android:orientation="vertical">
 
         <androidx.recyclerview.widget.RecyclerView
@@ -91,18 +89,16 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="4dp"
-        android:layout_marginStart="16dp"
-        android:layout_marginBottom="24dp"
-        android:layout_marginEnd="16dp"
-        android:background="@color/media_dialog_background"
+        android:layout_marginStart="@dimen/dialog_side_padding"
+        android:layout_marginEnd="@dimen/dialog_side_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
         android:orientation="horizontal">
 
         <Button
             android:id="@+id/stop"
-            style="@style/MediaOutputRoundedOutlinedButton"
+            style="@style/Widget.Dialog.Button.BorderButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="0dp"
             android:text="@string/keyboard_key_media_stop"
             android:visibility="gone"/>
 
@@ -113,10 +109,9 @@
 
         <Button
             android:id="@+id/done"
-            style="@style/MediaOutputRoundedButton"
+            style="@style/Widget.Dialog.Button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="0dp"
             android:text="@string/inline_done_button"/>
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index e43a149..f57d65a 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -27,10 +27,10 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingStart="24dp"
-            android:paddingEnd="24dp"
-            android:paddingTop="26dp"
-            android:paddingBottom="30dp"
+            android:paddingStart="@dimen/dialog_side_padding"
+            android:paddingEnd="@dimen/dialog_side_padding"
+            android:paddingTop="@dimen/dialog_top_padding"
+            android:paddingBottom="@dimen/dialog_bottom_padding"
             android:orientation="vertical">
 
             <!-- Header -->
@@ -143,10 +143,7 @@
                     android:layout_weight="0"
                     android:layout_gravity="start"
                     android:text="@string/cancel"
-                    android:textColor="?android:textColorPrimary"
-                    android:background="@drawable/screenrecord_button_background_outline"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textSize="14sp"/>
+                    style="@style/Widget.Dialog.Button.BorderButton" />
                 <Space
                     android:layout_width="0dp"
                     android:layout_height="match_parent"
@@ -158,10 +155,7 @@
                     android:layout_weight="0"
                     android:layout_gravity="end"
                     android:text="@string/screenrecord_start"
-                    android:textColor="@android:color/system_neutral1_900"
-                    android:background="@drawable/screenrecord_button_background_solid"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textSize="14sp"/>
+                    style="@style/Widget.Dialog.Button" />
             </LinearLayout>
         </LinearLayout>
     </ScrollView>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c434285..9d4c2c3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -67,10 +67,6 @@
 
     <!-- media output dialog-->
     <color name="media_dialog_background">@android:color/system_neutral1_900</color>
-    <color name="media_dialog_solid_button_background">@android:color/system_accent1_100</color>
-    <color name="media_dialog_solid_button_text">@android:color/system_accent2_800</color>
-    <color name="media_dialog_outlined_button">@android:color/system_accent1_100</color>
-    <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_50</color>
     <color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color>
     <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color>
     <color name="media_dialog_item_status">@android:color/system_accent1_100</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 11865a5..8cad5b3 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -174,10 +174,6 @@
 
     <!-- media output dialog-->
     <color name="media_dialog_background" android:lstar="98">@android:color/system_neutral1_100</color>
-    <color name="media_dialog_solid_button_background">@android:color/system_accent1_600</color>
-    <color name="media_dialog_solid_button_text">@android:color/system_neutral1_50</color>
-    <color name="media_dialog_outlined_button">@android:color/system_accent1_600</color>
-    <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_900</color>
     <color name="media_dialog_active_item_main_content">@android:color/system_accent1_900</color>
     <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_600</color>
     <color name="media_dialog_item_status">@android:color/system_accent1_900</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c09658b..c7350a1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1098,8 +1098,6 @@
     <dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
     <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
     <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
-    <dimen name="media_output_dialog_button_padding_horizontal">16dp</dimen>
-    <dimen name="media_output_dialog_button_padding_vertical">8dp</dimen>
 
     <!-- Distance that the full shade transition takes in order for qs to fully transition to the
          shade -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 69f9966..9358349 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -200,9 +200,9 @@
 
     <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
 
-    <style name="TextAppearance.AuthCredential"
-        parent="@android:style/TextAppearance.DeviceDefault">
+    <style name="TextAppearance.AuthCredential">
         <item name="android:accessibilityLiveRegion">polite</item>
+        <item name="android:gravity">center_horizontal</item>
         <item name="android:textAlignment">gravity</item>
         <item name="android:layout_gravity">top</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -210,57 +210,44 @@
 
     <style name="TextAppearance.AuthCredential.Title">
         <item name="android:fontFamily">google-sans</item>
-        <item name="android:layout_marginTop">20dp</item>
-        <item name="android:textSize">36sp</item>
+        <item name="android:paddingTop">12dp</item>
+        <item name="android:paddingHorizontal">24dp</item>
+        <item name="android:textSize">24sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Subtitle">
         <item name="android:fontFamily">google-sans</item>
-        <item name="android:layout_marginTop">20dp</item>
-        <item name="android:textSize">18sp</item>
+        <item name="android:paddingTop">8dp</item>
+        <item name="android:paddingHorizontal">24dp</item>
+        <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Description">
         <item name="android:fontFamily">google-sans</item>
-        <item name="android:layout_marginTop">20dp</item>
-        <item name="android:textSize">16sp</item>
+        <item name="android:paddingTop">8dp</item>
+        <item name="android:paddingHorizontal">24dp</item>
+        <item name="android:textSize">14sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Error">
         <item name="android:paddingTop">6dp</item>
-        <item name="android:paddingBottom">18dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/colorError</item>
-        <item name="android:gravity">center</item>
     </style>
 
-    <style name="TextAppearance.AuthCredential.PasswordEntry">
+    <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:gravity">center</item>
         <item name="android:singleLine">true</item>
         <item name="android:textColor">?android:attr/colorForeground</item>
         <item name="android:textSize">24sp</item>
     </style>
 
-    <style name="AuthCredentialHeaderStyle">
-        <item name="android:paddingStart">48dp</item>
-        <item name="android:paddingEnd">24dp</item>
-        <item name="android:paddingTop">28dp</item>
-        <item name="android:paddingBottom">20dp</item>
-        <item name="android:orientation">vertical</item>
-        <item name="android:layout_gravity">top</item>
-    </style>
-
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
         <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
     </style>
 
-    <style name="AuthCredentialPasswordTheme" parent="@style/Theme.MaterialComponents.DayNight">
-        <item name="colorPrimary">?android:attr/colorPrimary</item>
-        <item name="colorPrimaryDark">?android:attr/colorPrimary</item>
-    </style>
-
     <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
 
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
@@ -320,8 +307,9 @@
         <item name="android:maxWidth">420dp</item>
         <item name="android:minHeight">0dp</item>
         <item name="android:minWidth">0dp</item>
-        <item name="android:paddingHorizontal">60dp</item>
-        <item name="android:paddingBottom">40dp</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:paddingHorizontal">44dp</item>
+        <item name="android:paddingTop">0dp</item>
     </style>
 
     <style name="LockPatternStyle">
@@ -466,20 +454,6 @@
         <item name="android:colorBackground">@color/media_dialog_background</item>
     </style>
 
-    <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
-        <item name="android:background">@drawable/media_output_dialog_button_background</item>
-        <item name="android:textColor">@color/media_dialog_outlined_button_text</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
-    </style>
-
-    <style name="MediaOutputRoundedButton" parent="@android:style/Widget.Material.Button">
-        <item name="android:background">@drawable/media_output_dialog_solid_button_background</item>
-        <item name="android:textColor">@color/media_dialog_solid_button_text</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
-    </style>
-
     <style name="MediaOutputItemInactiveTitle">
         <item name="android:textSize">16sp</item>
         <item name="android:textColor">@color/media_dialog_inactive_item_main_content</item>
@@ -912,13 +886,18 @@
         <item name="android:textAlignment">center</item>
     </style>
 
-    <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
+
+    <style name="Widget" />
+    <style name="Widget.Dialog" />
+    <style name="Widget.Dialog.Button">
+        <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:stateListAnimator">@null</item>
+        <item name="android:minWidth">0dp</item>
     </style>
 
     <style name="Widget.Dialog.Button.BorderButton">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
similarity index 61%
rename from packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
rename to packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
index 26c6a4b..195ba465 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -18,28 +18,24 @@
 /**
  * Plugin for loading flag values
  */
-interface FlagReader {
-    /** Returns a boolean value for the given flag.  */
-    fun isEnabled(flag: BooleanFlag): Boolean {
-        return flag.default
-    }
-
-    fun isEnabled(flag: ResourceBooleanFlag): Boolean
-
-    /** Returns a boolean value for the given flag.  */
-    fun isEnabled(id: Int, def: Boolean): Boolean {
-        return def
-    }
-
-    /** Add a listener to be alerted when any flag changes.  */
-    fun addListener(listener: Listener) {}
+interface FlagListenable {
+    /** Add a listener to be alerted when the given flag changes.  */
+    fun addListener(flag: Flag<*>, listener: Listener)
 
     /** Remove a listener to be alerted when any flag changes.  */
-    fun removeListener(listener: Listener) {}
+    fun removeListener(listener: Listener)
 
     /** A simple listener to be alerted when a flag changes.  */
     fun interface Listener {
-        /**  */
-        fun onFlagChanged(id: Int)
+        /** Called when the flag changes */
+        fun onFlagChanged(event: FlagEvent)
+    }
+
+    /** An event representing the change */
+    interface FlagEvent {
+        /** the id of the flag which changed */
+        val flagId: Int
+        /** if all listeners alerted invoke this method, the restart will be skipped */
+        fun requestNoRestart()
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index b2ca2d7..ec619dd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -24,30 +24,39 @@
 import android.net.Uri
 import android.os.Bundle
 import android.os.Handler
-import android.provider.Settings
 import androidx.concurrent.futures.CallbackToFutureAdapter
 import com.google.common.util.concurrent.ListenableFuture
-import org.json.JSONException
-import org.json.JSONObject
+import java.util.function.Consumer
 
 class FlagManager constructor(
     private val context: Context,
+    private val settings: FlagSettingsHelper,
     private val handler: Handler
-) : FlagReader {
+) : FlagListenable {
     companion object {
         const val RECEIVING_PACKAGE = "com.android.systemui"
         const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
-        const val FIELD_ID = "id"
-        const val FIELD_VALUE = "value"
-        const val FIELD_TYPE = "type"
-        const val FIELD_FLAGS = "flags"
-        const val TYPE_BOOLEAN = "boolean"
+        const val EXTRA_ID = "id"
+        const val EXTRA_VALUE = "value"
+        const val EXTRA_FLAGS = "flags"
         private const val SETTINGS_PREFIX = "systemui/flags"
     }
 
-    private val listeners: MutableSet<FlagReader.Listener> = mutableSetOf()
+    constructor(context: Context, handler: Handler) : this(
+        context,
+        FlagSettingsHelper(context.contentResolver),
+        handler
+    )
+
+    /**
+     * An action called on restart which takes as an argument whether the listeners requested
+     * that the restart be suppressed
+     */
+    var restartAction: Consumer<Boolean>? = null
+    var clearCacheAction: Consumer<Int>? = null
+    private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
     private val settingsObserver: ContentObserver = SettingsObserver()
 
     fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
@@ -61,7 +70,7 @@
                         override fun onReceive(context: Context, intent: Intent) {
                             val extras: Bundle? = getResultExtras(false)
                             val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
-                                extras?.getParcelableArrayList(FIELD_FLAGS)
+                                extras?.getParcelableArrayList(EXTRA_FLAGS)
                             if (listOfFlags != null) {
                                 completer.set(listOfFlags)
                             } else {
@@ -73,9 +82,19 @@
         } as ListenableFuture<Collection<Flag<*>>>
     }
 
+    /**
+     * Returns the stored value or null if not set.
+     * This API is used by TheFlippinApp.
+     */
+    fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer)
+
+    /**
+     * Sets the value of a boolean flag.
+     * This API is used by TheFlippinApp.
+     */
     fun setFlagValue(id: Int, enabled: Boolean) {
         val intent = createIntent(id)
-        intent.putExtra(FIELD_VALUE, enabled)
+        intent.putExtra(EXTRA_VALUE, enabled)
 
         context.sendBroadcast(intent)
     }
@@ -86,49 +105,30 @@
         context.sendBroadcast(intent)
     }
 
-    override fun isEnabled(id: Int, def: Boolean): Boolean {
-        return isEnabled(id) ?: def
-    }
-
     /** Returns the stored value or null if not set.  */
-    fun isEnabled(id: Int): Boolean? {
-        val data: String? = Settings.Secure.getString(
-            context.contentResolver, keyToSettingsPrefix(id))
-        if (data == null || data?.isEmpty()) {
-            return null
-        }
-        val json: JSONObject
-        try {
-            json = JSONObject(data)
-            return if (!assertType(json, TYPE_BOOLEAN)) {
-                null
-            } else json.getBoolean(FIELD_VALUE)
-        } catch (e: JSONException) {
-            throw InvalidFlagStorageException()
-        }
+    fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
+        val data = settings.getString(idToSettingsKey(id))
+        return serializer.fromSettingsData(data)
     }
 
-    override fun isEnabled(flag: ResourceBooleanFlag): Boolean {
-        throw RuntimeException("Not implemented in FlagManager")
-    }
-
-    override fun addListener(listener: FlagReader.Listener) {
+    override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
-            listeners.add(listener)
+            listeners.add(PerFlagListener(flag.id, listener))
             if (registerNeeded) {
-                context.contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(SETTINGS_PREFIX), true, settingsObserver)
+                settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
             }
         }
     }
 
-    override fun removeListener(listener: FlagReader.Listener) {
+    override fun removeListener(listener: FlagListenable.Listener) {
         synchronized(listeners) {
-            val isRegistered = !listeners.isEmpty()
-            listeners.remove(listener)
-            if (isRegistered && listeners.isEmpty()) {
-                context.contentResolver.unregisterContentObserver(settingsObserver)
+            if (listeners.isEmpty()) {
+                return
+            }
+            listeners.removeIf { it.listener == listener }
+            if (listeners.isEmpty()) {
+                settings.unregisterContentObserver(settingsObserver)
             }
         }
     }
@@ -136,21 +136,13 @@
     private fun createIntent(id: Int): Intent {
         val intent = Intent(ACTION_SET_FLAG)
         intent.setPackage(RECEIVING_PACKAGE)
-        intent.putExtra(FIELD_ID, id)
+        intent.putExtra(EXTRA_ID, id)
 
         return intent
     }
 
-    fun keyToSettingsPrefix(key: Int): String {
-        return SETTINGS_PREFIX + "/" + key
-    }
-
-    private fun assertType(json: JSONObject, type: String): Boolean {
-        return try {
-            json.getString(FIELD_TYPE) == TYPE_BOOLEAN
-        } catch (e: JSONException) {
-            false
-        }
+    fun idToSettingsKey(id: Int): String {
+        return "$SETTINGS_PREFIX/$id"
     }
 
     inner class SettingsObserver : ContentObserver(handler) {
@@ -160,17 +152,40 @@
             }
             val parts = uri.pathSegments
             val idStr = parts[parts.size - 1]
-            try {
-                val id = idStr.toInt()
-                listeners.forEach { l -> l.onFlagChanged(id) }
-            } catch (e: NumberFormatException) {
-                // no-op
-            }
+            val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
+            clearCacheAction?.accept(id)
+            dispatchListenersAndMaybeRestart(id)
         }
     }
-}
 
-class InvalidFlagStorageException : Exception("Data found but is invalid")
+    fun dispatchListenersAndMaybeRestart(id: Int) {
+        val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
+            listeners.mapNotNull { if (it.id == id) it.listener else null }
+        }
+        // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
+        if (filteredListeners.isEmpty()) {
+            restartAction?.accept(false)
+            return
+        }
+        // Dispatch to every listener and save whether each one called requestNoRestart.
+        val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
+            var didRequestNoRestart = false
+            val event = object : FlagListenable.FlagEvent {
+                override val flagId = id
+                override fun requestNoRestart() {
+                    didRequestNoRestart = true
+                }
+            }
+            listener.onFlagChanged(event)
+            didRequestNoRestart
+        }
+        // Suppress restart only if ALL listeners request it.
+        val suppressRestart = suppressRestartList.all { it }
+        restartAction?.accept(suppressRestart)
+    }
+
+    private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener)
+}
 
 class NoFlagResultsException : Exception(
     "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
new file mode 100644
index 0000000..e9ea19d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.util.Log
+import org.json.JSONException
+import org.json.JSONObject
+
+private const val FIELD_VALUE = "value"
+private const val FIELD_TYPE = "type"
+private const val TYPE_BOOLEAN = "boolean"
+private const val TYPE_STRING = "string"
+
+private const val TAG = "FlagSerializer"
+
+abstract class FlagSerializer<T>(
+    private val type: String,
+    private val setter: (JSONObject, String, T) -> Unit,
+    private val getter: (JSONObject, String) -> T
+) {
+    fun toSettingsData(value: T): String? {
+        return try {
+            JSONObject()
+                .put(FIELD_TYPE, type)
+                .also { setter(it, FIELD_VALUE, value) }
+                .toString()
+        } catch (e: JSONException) {
+            Log.w(TAG, "write error", e)
+            null
+        }
+    }
+
+    /**
+     * @throws InvalidFlagStorageException
+     */
+    fun fromSettingsData(data: String?): T? {
+        if (data == null || data.isEmpty()) {
+            return null
+        }
+        try {
+            val json = JSONObject(data)
+            return if (json.getString(FIELD_TYPE) == type) {
+                getter(json, FIELD_VALUE)
+            } else {
+                null
+            }
+        } catch (e: JSONException) {
+            Log.w(TAG, "read error", e)
+            throw InvalidFlagStorageException()
+        }
+    }
+}
+
+object BooleanFlagSerializer : FlagSerializer<Boolean>(
+    TYPE_BOOLEAN,
+    JSONObject::put,
+    JSONObject::getBoolean
+)
+
+object StringFlagSerializer : FlagSerializer<String>(
+    TYPE_STRING,
+    JSONObject::put,
+    JSONObject::getString
+)
+
+class InvalidFlagStorageException : Exception("Data found but is invalid")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
new file mode 100644
index 0000000..742bb0b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+
+class FlagSettingsHelper(private val contentResolver: ContentResolver) {
+
+    fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key)
+
+    fun registerContentObserver(
+        name: String,
+        notifyForDescendants: Boolean,
+        observer: ContentObserver
+    ) {
+        contentResolver.registerContentObserver(
+            Settings.Secure.getUriFor(name),
+            notifyForDescendants,
+            observer
+        )
+    }
+
+    fun unregisterContentObserver(observer: ContentObserver) {
+        contentResolver.unregisterContentObserver(observer)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index e46b6f1..ea93a3b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -60,14 +60,12 @@
         hingeAngleProvider,
         screenStatusProvider,
         deviceStateManager,
-        mainExecutor
+        mainExecutor,
+        mainHandler
     )
 
     val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
-        PhysicsBasedUnfoldTransitionProgressProvider(
-            mainHandler,
-            foldStateProvider
-        )
+        PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
     } else {
         FixedTimingTransitionProgressProvider(foldStateProvider)
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 90f5998..51eae57 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.unfold.progress
 
-import android.os.Handler
 import android.util.Log
 import android.util.MathUtils.saturate
 import androidx.dynamicanimation.animation.DynamicAnimation
@@ -24,9 +23,10 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -39,7 +39,6 @@
  *  - doesn't handle postures
  */
 internal class PhysicsBasedUnfoldTransitionProgressProvider(
-    private val handler: Handler,
     private val foldStateProvider: FoldStateProvider
 ) :
     UnfoldTransitionProgressProvider,
@@ -51,8 +50,6 @@
             addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
         }
 
-    private val timeoutRunnable = TimeoutRunnable()
-
     private var isTransitionRunning = false
     private var isAnimatedCancelRunning = false
 
@@ -92,7 +89,7 @@
                     cancelTransition(endValue = 1f, animate = true)
                 }
             }
-            FOLD_UPDATE_FINISH_FULL_OPEN -> {
+            FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> {
                 // Do not cancel if we haven't started the transition yet.
                 // This could happen when we fully unfolded the device before the screen
                 // became available. In this case we start and immediately cancel the animation
@@ -106,7 +103,11 @@
                 cancelTransition(endValue = 0f, animate = false)
             }
             FOLD_UPDATE_START_CLOSING -> {
-                startTransition(startValue = 1f)
+                // The transition might be already running as the device might start closing several
+                // times before reaching an end state.
+                if (!isTransitionRunning) {
+                    startTransition(startValue = 1f)
+                }
             }
         }
 
@@ -116,8 +117,6 @@
     }
 
     private fun cancelTransition(endValue: Float, animate: Boolean) {
-        handler.removeCallbacks(timeoutRunnable)
-
         if (isTransitionRunning && animate) {
             isAnimatedCancelRunning = true
             springAnimation.animateToFinalPosition(endValue)
@@ -175,8 +174,6 @@
         }
 
         springAnimation.start()
-
-        handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -187,13 +184,6 @@
         listeners.remove(listener)
     }
 
-    private inner class TimeoutRunnable : Runnable {
-
-        override fun run() {
-            cancelTransition(endValue = 1f, animate = true)
-        }
-    }
-
     private object AnimationProgressProperty :
         FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
 
@@ -212,7 +202,6 @@
 private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
 private const val DEBUG = true
 
-private const val TRANSITION_TIMEOUT_MILLIS = 2000L
 private const val SPRING_STIFFNESS = 200.0f
 private const val MINIMAL_VISIBLE_CHANGE = 0.001f
 private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 35e2b30..6d9631c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,14 +15,19 @@
  */
 package com.android.systemui.unfold.updates
 
+import android.annotation.FloatRange
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
 import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import java.util.concurrent.Executor
 
 class DeviceFoldStateProvider(
@@ -30,7 +35,8 @@
     private val hingeAngleProvider: HingeAngleProvider,
     private val screenStatusProvider: ScreenStatusProvider,
     private val deviceStateManager: DeviceStateManager,
-    private val mainExecutor: Executor
+    private val mainExecutor: Executor,
+    private val handler: Handler
 ) : FoldStateProvider {
 
     private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@@ -38,9 +44,13 @@
     @FoldUpdate
     private var lastFoldUpdate: Int? = null
 
+    @FloatRange(from = 0.0, to = 180.0)
+    private var lastHingeAngle: Float = 0f
+
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
     private val foldStateListener = FoldStateListener(context)
+    private val timeoutRunnable = TimeoutRunnable()
 
     private var isFolded = false
     private var isUnfoldHandled = true
@@ -72,47 +82,69 @@
     override val isFullyOpened: Boolean
         get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
 
+    private val isTransitionInProgess: Boolean
+        get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
+                lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
     private fun onHingeAngle(angle: Float) {
-        when (lastFoldUpdate) {
-            FOLD_UPDATE_FINISH_FULL_OPEN -> {
-                if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_START_CLOSING
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
-                }
-            }
-            FOLD_UPDATE_START_OPENING -> {
-                if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
-                }
-            }
-            FOLD_UPDATE_START_CLOSING -> {
-                if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
-                }
+        if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
+
+        val isClosing = angle < lastHingeAngle
+        val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
+        val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
+        if (isClosing && !closingEventDispatched && !isFullyOpened) {
+            notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
+        }
+
+        if (isTransitionInProgess) {
+            if (isFullyOpened) {
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+                cancelTimeout()
+            } else {
+                // The timeout will trigger some constant time after the last angle update.
+                rescheduleAbortAnimationTimeout()
             }
         }
 
+        lastHingeAngle = angle
         outputListeners.forEach { it.onHingeAngleUpdate(angle) }
     }
 
     private inner class FoldStateListener(context: Context) :
         DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
             isFolded = folded
+            lastHingeAngle = FULLY_CLOSED_DEGREES
 
             if (folded) {
-                lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
-                outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
                 hingeAngleProvider.stop()
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+                cancelTimeout()
                 isUnfoldHandled = false
             } else {
-                lastFoldUpdate = FOLD_UPDATE_START_OPENING
-                outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
+                notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+                rescheduleAbortAnimationTimeout()
                 hingeAngleProvider.start()
             }
         })
 
+    private fun notifyFoldUpdate(@FoldUpdate update: Int) {
+        if (DEBUG) { Log.d(TAG, stateToString(update)) }
+        outputListeners.forEach { it.onFoldUpdate(update) }
+        lastFoldUpdate = update
+    }
+
+    private fun rescheduleAbortAnimationTimeout() {
+        if (isTransitionInProgess) {
+            cancelTimeout()
+        }
+        handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS)
+    }
+
+    private fun cancelTimeout() {
+        handler.removeCallbacks(timeoutRunnable)
+    }
+
     private inner class ScreenStatusListener :
         ScreenStatusProvider.ScreenListener {
 
@@ -136,7 +168,39 @@
             onHingeAngle(angle)
         }
     }
+
+    private inner class TimeoutRunnable : Runnable {
+
+        override fun run() {
+            notifyFoldUpdate(FOLD_UPDATE_ABORTED)
+        }
+    }
 }
 
-private const val START_CLOSING_THRESHOLD_DEGREES = 95f
-private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
+private fun stateToString(@FoldUpdate update: Int): String {
+    return when (update) {
+        FOLD_UPDATE_START_OPENING -> "START_OPENING"
+        FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN"
+        FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
+        FOLD_UPDATE_ABORTED -> "ABORTED"
+        FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
+        FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
+        FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
+        FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
+        else -> "UNKNOWN"
+    }
+}
+
+private const val TAG = "DeviceFoldProvider"
+private const val DEBUG = false
+
+/**
+ * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or
+ * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
+ */
+@VisibleForTesting
+const val ABORT_CLOSING_MILLIS = 1000L
+
+/** Threshold after which we consider the device fully unfolded. */
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 643ece3..bffebcd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -39,6 +39,7 @@
         FOLD_UPDATE_START_OPENING,
         FOLD_UPDATE_HALF_OPEN,
         FOLD_UPDATE_START_CLOSING,
+        FOLD_UPDATE_ABORTED,
         FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
         FOLD_UPDATE_FINISH_HALF_OPEN,
         FOLD_UPDATE_FINISH_FULL_OPEN,
@@ -51,7 +52,8 @@
 const val FOLD_UPDATE_START_OPENING = 0
 const val FOLD_UPDATE_HALF_OPEN = 1
 const val FOLD_UPDATE_START_CLOSING = 2
-const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
-const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
-const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
-const val FOLD_UPDATE_FINISH_CLOSED = 6
+const val FOLD_UPDATE_ABORTED = 3
+const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
+const val FOLD_UPDATE_FINISH_CLOSED = 7
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 10ceee9..adfc872 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,7 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import java.util.function.Supplier
 
 @Module(includes = [
     SettingsUtilModule::class
@@ -38,5 +39,9 @@
         fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
             return FlagManager(context, handler)
         }
+
+        @JvmStatic
+        @Provides
+        fun providesFlagCollector(): Supplier<Map<Int, Flag<*>>>? = null
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
new file mode 100644
index 0000000..96a90df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * See [Flags] for instructions on defining new flags.
+ */
+interface FeatureFlags : FlagListenable {
+    /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: BooleanFlag): Boolean
+
+    /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: ResourceBooleanFlag): Boolean
+
+    /** Returns a string value for the given flag.  */
+    fun getString(flag: StringFlag): String
+
+    /** Returns a string value for the given flag.  */
+    fun getString(flag: ResourceStringFlag): String
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 3f00b87..89623f4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -18,11 +18,12 @@
 
 import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
 import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
-import static com.android.systemui.flags.FlagManager.FIELD_FLAGS;
-import static com.android.systemui.flags.FlagManager.FIELD_ID;
-import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
+import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
+import static com.android.systemui.flags.FlagManager.EXTRA_ID;
+import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
 
-import android.annotation.Nullable;
+import static java.util.Objects.requireNonNull;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +33,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
@@ -39,14 +41,13 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.settings.SecureSettings;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Supplier;
 
 import javax.inject.Inject;
 
@@ -66,7 +67,9 @@
     private final FlagManager mFlagManager;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
-    private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
+    private final Supplier<Map<Integer, Flag<?>>> mFlagsCollector;
+    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
+    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
 
     @Inject
     public FeatureFlagsDebug(
@@ -74,99 +77,135 @@
             Context context,
             SecureSettings secureSettings,
             @Main Resources resources,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Nullable Supplier<Map<Integer, Flag<?>>> flagsCollector) {
         mFlagManager = flagManager;
         mSecureSettings = secureSettings;
         mResources = resources;
+        mFlagsCollector = flagsCollector != null ? flagsCollector : Flags::collectFlags;
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
+        flagManager.setRestartAction(this::restartSystemUI);
+        flagManager.setClearCacheAction(this::removeFromCache);
         context.registerReceiver(mReceiver, filter, null, null);
         dumpManager.registerDumpable(TAG, this);
     }
 
     @Override
-    public boolean isEnabled(BooleanFlag flag) {
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(id, isEnabled(id, flag.getDefault()));
+            mBooleanFlagCache.put(id,
+                    readFlagValue(id, flag.getDefault(), BooleanFlagSerializer.INSTANCE));
         }
 
         return mBooleanFlagCache.get(id);
     }
 
     @Override
-    public boolean isEnabled(ResourceBooleanFlag flag) {
+    public boolean isEnabled(@NonNull ResourceBooleanFlag flag) {
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(
-                    id, isEnabled(id, mResources.getBoolean(flag.getResourceId())));
+            mBooleanFlagCache.put(id,
+                    readFlagValue(id, mResources.getBoolean(flag.getResourceId()),
+                            BooleanFlagSerializer.INSTANCE));
         }
 
         return mBooleanFlagCache.get(id);
     }
 
-    /** Return a {@link BooleanFlag}'s value. */
+    @NonNull
     @Override
-    public boolean isEnabled(int id, boolean defaultValue) {
-        Boolean result = isEnabledInternal(id);
+    public String getString(@NonNull StringFlag flag) {
+        int id = flag.getId();
+        if (!mStringFlagCache.containsKey(id)) {
+            mStringFlagCache.put(id,
+                    readFlagValue(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+        }
+
+        return mStringFlagCache.get(id);
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull ResourceStringFlag flag) {
+        int id = flag.getId();
+        if (!mStringFlagCache.containsKey(id)) {
+            mStringFlagCache.put(id,
+                    readFlagValue(id, mResources.getString(flag.getResourceId()),
+                            StringFlagSerializer.INSTANCE));
+        }
+
+        return mStringFlagCache.get(id);
+    }
+
+    @NonNull
+    private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+        requireNonNull(defaultValue, "defaultValue");
+        T result = readFlagValueInternal(id, serializer);
         return result == null ? defaultValue : result;
     }
 
 
     /** Returns the stored value or null if not set. */
-    private Boolean isEnabledInternal(int id) {
+    @Nullable
+    private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
         try {
-            return mFlagManager.isEnabled(id);
+            return mFlagManager.readFlagValue(id, serializer);
         } catch (Exception e) {
             eraseInternal(id);
         }
         return null;
     }
 
-    /** Set whether a given {@link BooleanFlag} is enabled or not. */
-    public void setEnabled(int id, boolean value) {
-        Boolean currentValue = isEnabledInternal(id);
-        if (currentValue != null && currentValue == value) {
+    private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) {
+        requireNonNull(value, "Cannot set a null value");
+        T currentValue = readFlagValueInternal(id, serializer);
+        if (Objects.equals(currentValue, value)) {
+            Log.i(TAG, "Flag id " + id + " is already " + value);
             return;
         }
-
-        JSONObject json = new JSONObject();
-        try {
-            json.put(FlagManager.FIELD_TYPE, FlagManager.TYPE_BOOLEAN);
-            json.put(FIELD_VALUE, value);
-            mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), json.toString());
-            Log.i(TAG, "Set id " + id + " to " + value);
-            restartSystemUI();
-        } catch (JSONException e) {
-            // no-op
+        final String data = serializer.toSettingsData(value);
+        if (data == null) {
+            Log.w(TAG, "Failed to set id " + id + " to " + value);
+            return;
         }
+        mSecureSettings.putString(mFlagManager.idToSettingsKey(id), data);
+        Log.i(TAG, "Set id " + id + " to " + value);
+        removeFromCache(id);
+        mFlagManager.dispatchListenersAndMaybeRestart(id);
     }
 
     /** Erase a flag's overridden value if there is one. */
     public void eraseFlag(int id) {
         eraseInternal(id);
-        restartSystemUI();
+        removeFromCache(id);
+        mFlagManager.dispatchListenersAndMaybeRestart(id);
     }
 
     /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
     private void eraseInternal(int id) {
         // We can't actually "erase" things from sysprops, but we can set them to empty!
-        mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), "");
+        mSecureSettings.putString(mFlagManager.idToSettingsKey(id), "");
         Log.i(TAG, "Erase id " + id);
     }
 
     @Override
-    public void addListener(Listener run) {
-        mFlagManager.addListener(run);
+    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+        mFlagManager.addListener(flag, listener);
     }
 
     @Override
-    public void removeListener(Listener run) {
-        mFlagManager.removeListener(run);
+    public void removeListener(@NonNull Listener listener) {
+        mFlagManager.removeListener(listener);
     }
 
-    private void restartSystemUI() {
+    private void restartSystemUI(boolean requestSuppress) {
+        if (requestSuppress) {
+            Log.i(TAG, "SystemUI Restart Suppressed");
+            return;
+        }
         Log.i(TAG, "Restarting SystemUI");
         // SysUI starts back when up exited. Is there a better way to do this?
         System.exit(0);
@@ -175,14 +214,14 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            String action = intent == null ? null : intent.getAction();
             if (action == null) {
                 return;
             }
             if (ACTION_SET_FLAG.equals(action)) {
                 handleSetFlag(intent.getExtras());
             } else if (ACTION_GET_FLAGS.equals(action)) {
-                Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
+                Map<Integer, Flag<?>> knownFlagMap = mFlagsCollector.get();
                 ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
 
                 // Convert all flags to parcelable flags.
@@ -196,32 +235,47 @@
 
                 Bundle extras =  getResultExtras(true);
                 if (extras != null) {
-                    extras.putParcelableArrayList(FIELD_FLAGS, pFlags);
+                    extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
                 }
             }
         }
 
         private void handleSetFlag(Bundle extras) {
-            int id = extras.getInt(FIELD_ID);
+            if (extras == null) {
+                Log.w(TAG, "No extras");
+                return;
+            }
+            int id = extras.getInt(EXTRA_ID);
             if (id <= 0) {
                 Log.w(TAG, "ID not set or less than  or equal to 0: " + id);
                 return;
             }
 
-            Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+            Map<Integer, Flag<?>> flagMap = mFlagsCollector.get();
             if (!flagMap.containsKey(id)) {
                 Log.w(TAG, "Tried to set unknown id: " + id);
                 return;
             }
             Flag<?> flag = flagMap.get(id);
 
-            if (!extras.containsKey(FIELD_VALUE)) {
+            if (!extras.containsKey(EXTRA_VALUE)) {
                 eraseFlag(id);
                 return;
             }
 
-            if (flag instanceof BooleanFlag) {
-                setEnabled(id, extras.getBoolean(FIELD_VALUE));
+            Object value = extras.get(EXTRA_VALUE);
+            if (flag instanceof BooleanFlag && value instanceof Boolean) {
+                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
+            } else  if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) {
+                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
+            } else if (flag instanceof StringFlag && value instanceof String) {
+                setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
+            } else if (flag instanceof ResourceStringFlag && value instanceof String) {
+                setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
+            } else {
+                Log.w(TAG,
+                        "Unable to set " + id + " of type " + flag.getClass() + " to value of type "
+                                + (value == null ? null : value.getClass()));
             }
         }
 
@@ -245,16 +299,18 @@
         }
     };
 
+    private void removeFromCache(int id) {
+        mBooleanFlagCache.remove(id);
+        mStringFlagCache.remove(id);
+    }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: true");
-        ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
-        for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
-            flagStrings.add("  sysui_flag_" + entry.getKey() + ": " + entry.getValue());
-        }
-        flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
-        for (String flagString : flagStrings) {
-            pw.println(flagString);
-        }
+        pw.println("booleans: " + mBooleanFlagCache.size());
+        mBooleanFlagCache.forEach((key, value) -> pw.println("  sysui_flag_" + key + ": " + value));
+        pw.println("Strings: " + mStringFlagCache.size());
+        mStringFlagCache.forEach((key, value) -> pw.println("  sysui_flag_" + key
+                + ": [length=" + value.length() + "] \"" + value + "\""));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 5b6404f..348a8e2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui.flags;
 
+import static java.util.Objects.requireNonNull;
+
 import android.content.res.Resources;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
@@ -40,7 +43,8 @@
 @SysUISingleton
 public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
     private final Resources mResources;
-    SparseBooleanArray mFlagCache = new SparseBooleanArray();
+    SparseBooleanArray mBooleanCache = new SparseBooleanArray();
+    SparseArray<String> mStringCache = new SparseArray<>();
     @Inject
     public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
         mResources = resources;
@@ -48,10 +52,10 @@
     }
 
     @Override
-    public void addListener(Listener run) {}
+    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
 
     @Override
-    public void removeListener(Listener run) {}
+    public void removeListener(@NonNull Listener listener) {}
 
     @Override
     public boolean isEnabled(BooleanFlag flag) {
@@ -60,27 +64,57 @@
 
     @Override
     public boolean isEnabled(ResourceBooleanFlag flag) {
-        int cacheIndex = mFlagCache.indexOfKey(flag.getId());
+        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
         if (cacheIndex < 0) {
             return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
         }
 
-        return mFlagCache.valueAt(cacheIndex);
+        return mBooleanCache.valueAt(cacheIndex);
     }
 
+    private boolean isEnabled(int key, boolean defaultValue) {
+        mBooleanCache.append(key, defaultValue);
+        return defaultValue;
+    }
+
+    @NonNull
     @Override
-    public boolean isEnabled(int key, boolean defaultValue) {
-        mFlagCache.append(key, defaultValue);
+    public String getString(@NonNull StringFlag flag) {
+        return getString(flag.getId(), flag.getDefault());
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull ResourceStringFlag flag) {
+        int cacheIndex = mStringCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return getString(flag.getId(),
+                    requireNonNull(mResources.getString(flag.getResourceId())));
+        }
+
+        return mStringCache.valueAt(cacheIndex);
+    }
+
+    private String getString(int key, String defaultValue) {
+        mStringCache.append(key, defaultValue);
         return defaultValue;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
-        int size = mFlagCache.size();
-        for (int i = 0; i < size; i++) {
-            pw.println("  sysui_flag_" + mFlagCache.keyAt(i)
-                    + ": " + mFlagCache.valueAt(i));
+        int numBooleans = mBooleanCache.size();
+        pw.println("booleans: " + numBooleans);
+        for (int i = 0; i < numBooleans; i++) {
+            pw.println("  sysui_flag_" + mBooleanCache.keyAt(i) + ": " + mBooleanCache.valueAt(i));
+        }
+        int numStrings = mStringCache.size();
+        pw.println("Strings: " + numStrings);
+        for (int i = 0; i < numStrings; i++) {
+            final int id = mStringCache.keyAt(i);
+            final String value = mStringCache.valueAt(i);
+            final int length = value.length();
+            pw.println("  sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\"");
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f74fbf4..7f5744c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -85,7 +85,9 @@
 
     public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
         super(context, R.style.Theme_SystemUI_Dialog_Media);
-        mContext = context;
+
+        // Save the context that is wrapped with our theme.
+        mContext = getContext();
         mMediaOutputController = mediaOutputController;
         mLayoutManager = new LinearLayoutManager(mContext);
         mListMaxHeight = context.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index dab0efe..e93c349 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -259,6 +259,12 @@
     }
 
     public static class Item {
+        public Item(int iconResId, CharSequence line1, Object tag) {
+            this.iconResId = iconResId;
+            this.line1 = line1;
+            this.tag = tag;
+        }
+
         public int iconResId;
         public QSTile.Icon icon;
         public Drawable overlay;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 7e410d0..6c072f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -146,6 +146,10 @@
     }
 
     private static class TilePair {
+        private TilePair(QSTile tile) {
+            mTile = tile;
+        }
+
         QSTile mTile;
         boolean mReady = false;
     }
@@ -157,8 +161,7 @@
 
         TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
             for (QSTile tile: tilesToAdd) {
-                TilePair pair = new TilePair();
-                pair.mTile = tile;
+                TilePair pair = new TilePair(tile);
                 mQSTileList.add(pair);
             }
             mQSTileHost = host;
@@ -288,15 +291,11 @@
         if (mSpecs.contains(spec)) {
             return;
         }
-        TileInfo info = new TileInfo();
-        info.state = state;
-        info.state.dualTarget = false; // No dual targets in edit.
-        info.state.expandedAccessibilityClassName =
-                Button.class.getName();
-        info.spec = spec;
-        info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
+        state.dualTarget = false; // No dual targets in edit.
+        state.expandedAccessibilityClassName = Button.class.getName();
+        state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
                 ? null : appLabel;
-        info.isSystem = isSystem;
+        TileInfo info = new TileInfo(spec, state, isSystem);
         mTiles.add(info);
         mSpecs.add(spec);
     }
@@ -312,6 +311,12 @@
     }
 
     public static class TileInfo {
+        public TileInfo(String spec, QSTile.State state, boolean isSystem) {
+            this.spec = spec;
+            this.state = state;
+            this.isSystem = isSystem;
+        }
+
         public String spec;
         public QSTile.State state;
         public boolean isSystem;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 65b6617..6fcfc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -394,10 +394,11 @@
                 int count = 0;
                 for (CachedBluetoothDevice device : devices) {
                     if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
-                    final Item item = new Item();
-                    item.iconResId = com.android.internal.R.drawable.ic_qs_bluetooth;
-                    item.line1 = device.getName();
-                    item.tag = device;
+                    final Item item =
+                            new Item(
+                                    com.android.internal.R.drawable.ic_qs_bluetooth,
+                                    device.getName(),
+                                    device);
                     int state = device.getMaxConnectionState();
                     if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.iconResId = R.drawable.ic_bluetooth_connected;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index b83dc52..1fb608a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -402,11 +402,12 @@
                 // if we are connected, simply show that device
                 for (CastDevice device : devices) {
                     if (device.state == CastDevice.STATE_CONNECTED) {
-                        final Item item = new Item();
-                        item.iconResId = R.drawable.ic_cast_connected;
-                        item.line1 = getDeviceName(device);
+                        final Item item =
+                                new Item(
+                                        R.drawable.ic_cast_connected,
+                                        getDeviceName(device),
+                                        device);
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
-                        item.tag = device;
                         item.canDisconnect = true;
                         items = new Item[] { item };
                         break;
@@ -422,13 +423,11 @@
                     for (String id : mVisibleOrder.keySet()) {
                         final CastDevice device = mVisibleOrder.get(id);
                         if (!devices.contains(device)) continue;
-                        final Item item = new Item();
-                        item.iconResId = R.drawable.ic_cast;
-                        item.line1 = getDeviceName(device);
+                        final Item item =
+                                new Item(R.drawable.ic_cast, getDeviceName(device), device);
                         if (device.state == CastDevice.STATE_CONNECTING) {
                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
                         }
-                        item.tag = device;
                         items[i++] = item;
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7ba9cc2..a2577d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -98,7 +98,7 @@
                         toggleDataSaver();
                         Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
                     });
-            dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
+            dialog.setNeutralButton(com.android.internal.R.string.cancel, null);
             dialog.setShowForAllUsers(true);
 
             if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 83506b2..bb27458 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -190,7 +190,6 @@
                 case Settings.Secure.ZEN_DURATION_PROMPT:
                     mUiHandler.post(() -> {
                         Dialog dialog = makeZenModeDialog();
-                        SystemUIDialog.registerDismissListener(dialog);
                         if (view != null) {
                             mDialogLaunchAnimator.showFromView(dialog, view, false);
                         } else {
@@ -211,10 +210,12 @@
     }
 
     private Dialog makeZenModeDialog() {
-        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
-                .createDialog();
+        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+                true /* cancelIsNeutral */).createDialog();
         SystemUIDialog.applyFlags(dialog);
         SystemUIDialog.setShowForAllUsers(dialog, true);
+        SystemUIDialog.registerDismissListener(dialog);
+        SystemUIDialog.setDialogSize(dialog);
         return dialog;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index e79ca0c..da0069f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -378,26 +378,26 @@
 
         @Override
         public void onAccessPointsChanged(final List<WifiEntry> accessPoints) {
-            mAccessPoints = accessPoints.toArray(new WifiEntry[accessPoints.size()]);
-            filterUnreachableAPs();
+            mAccessPoints = filterUnreachableAPs(accessPoints);
 
             updateItems();
         }
 
         /** Filter unreachable APs from mAccessPoints */
-        private void filterUnreachableAPs() {
+        private WifiEntry[] filterUnreachableAPs(List<WifiEntry> unfiltered) {
             int numReachable = 0;
-            for (WifiEntry ap : mAccessPoints) {
+            for (WifiEntry ap : unfiltered) {
                 if (isWifiEntryReachable(ap)) numReachable++;
             }
-            if (numReachable != mAccessPoints.length) {
-                WifiEntry[] unfiltered = mAccessPoints;
-                mAccessPoints = new WifiEntry[numReachable];
+            if (numReachable != unfiltered.size()) {
+                WifiEntry[] accessPoints = new WifiEntry[numReachable];
                 int i = 0;
                 for (WifiEntry ap : unfiltered) {
-                    if (isWifiEntryReachable(ap)) mAccessPoints[i++] = ap;
+                    if (isWifiEntryReachable(ap)) accessPoints[i++] = ap;
                 }
+                return accessPoints;
             }
+            return unfiltered.toArray(new WifiEntry[0]);
         }
 
         @Override
@@ -454,10 +454,7 @@
                 items = new Item[mAccessPoints.length];
                 for (int i = 0; i < mAccessPoints.length; i++) {
                     final WifiEntry ap = mAccessPoints[i];
-                    final Item item = new Item();
-                    item.tag = ap;
-                    item.iconResId = mWifiController.getIcon(ap);
-                    item.line1 = ap.getSsid();
+                    final Item item = new Item(mWifiController.getIcon(ap), ap.getSsid(), ap);
                     item.line2 = ap.getSummary();
                     item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
                             ? R.drawable.qs_ic_wifi_lock
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index ba4257f..e3f085c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -159,7 +159,9 @@
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialog");
         }
-        mContext = context;
+
+        // Save the context that is wrapped with our theme.
+        mContext = getContext();
         mHandler = handler;
         mBackgroundExecutor = executor;
         mInternetDialogFactory = internetDialogFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 1fee1b4..7dd24b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -316,15 +316,11 @@
     }
 
     CharSequence getSubtitleText(boolean isProgressBarVisible) {
-        if (isAirplaneModeEnabled()) {
-            return null;
-        }
-
         if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
-            // When the airplane mode is off and Wi-Fi is disabled.
+            // When Wi-Fi is disabled.
             //   Sub-Title: Wi-Fi is off
             if (DEBUG) {
-                Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+                Log.d(TAG, "Wi-Fi off.");
             }
             return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
index c4fadff..4551807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
@@ -40,7 +40,7 @@
             super(context);
             setTitle(R.string.user_remove_user_title);
             setMessage(context.getString(R.string.user_remove_user_message));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(R.string.user_remove_user_remove), this);
@@ -51,7 +51,7 @@
 
         @Override
         public void onClick(DialogInterface dialog, int which) {
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 0389a7b..f500d39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -107,6 +108,7 @@
     private final LeakDetector mLeakDetector;
     private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
     private final IStatusBarService mStatusBarService;
+    private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
 
     private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
@@ -154,6 +156,7 @@
             LeakDetector leakDetector,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
+            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager
     ) {
         mLogger = logger;
@@ -164,6 +167,7 @@
         mLeakDetector = leakDetector;
         mFgsFeatureController = fgsFeatureController;
         mStatusBarService = statusBarService;
+        mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
     }
 
@@ -725,9 +729,10 @@
             return;
         }
         reapplyFilterAndSort(reason);
-        if (mPresenter != null && !mNotifPipelineFlags.isNewPipelineEnabled()) {
+        if (mPresenter != null) {
             mPresenter.updateNotificationViews(reason);
         }
+        mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
     }
 
     public void updateNotificationRanking(RankingMap rankingMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt
new file mode 100644
index 0000000..ef00627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import androidx.lifecycle.Observer
+
+/**
+ * An object which provides pieces of information about the notification shade.
+ *
+ * Note that individual fields of this object are updated together before synchronous observers are
+ * notified, so synchronous observers of two fields can be assured that they will see consistent
+ * results: e.g. if [hasActiveNotifs] is false then [activeNotifList] will be empty, and vice versa.
+ *
+ * This interface is read-only.
+ */
+interface NotifLiveDataStore {
+    val hasActiveNotifs: NotifLiveData<Boolean>
+    val activeNotifCount: NotifLiveData<Int>
+    val activeNotifList: NotifLiveData<List<NotificationEntry>>
+}
+
+/**
+ * An individual value which can be accessed directly, or observed for changes either synchronously
+ * or asynchronously.
+ *
+ * This interface is read-only.
+ */
+interface NotifLiveData<T> {
+    /** Access the current value */
+    val value: T
+    /** Add an observer which will be invoked synchronously when the value is changed. */
+    fun addSyncObserver(observer: Observer<T>)
+    /** Add an observer which will be invoked asynchronously after the value has changed */
+    fun addAsyncObserver(observer: Observer<T>)
+    /** Remove an observer previously added with [addSyncObserver] or [addAsyncObserver]. */
+    fun removeObserver(observer: Observer<T>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
new file mode 100644
index 0000000..8aa6b81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import androidx.lifecycle.Observer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.Assert
+import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.isNotEmpty
+import com.android.systemui.util.traceSection
+import java.util.Collections.unmodifiableList
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+import javax.inject.Inject
+
+/** Writeable implementation of [NotifLiveDataStore] */
+@SysUISingleton
+class NotifLiveDataStoreImpl @Inject constructor(
+    @Main private val mainExecutor: Executor
+) : NotifLiveDataStore {
+    private val hasActiveNotifsPrivate = NotifLiveDataImpl(
+        name = "hasActiveNotifs",
+        initialValue = false,
+        mainExecutor
+    )
+    private val activeNotifCountPrivate = NotifLiveDataImpl(
+        name = "activeNotifCount",
+        initialValue = 0,
+        mainExecutor
+    )
+    private val activeNotifListPrivate = NotifLiveDataImpl(
+        name = "activeNotifList",
+        initialValue = listOf<NotificationEntry>(),
+        mainExecutor
+    )
+
+    override val hasActiveNotifs: NotifLiveData<Boolean> = hasActiveNotifsPrivate
+    override val activeNotifCount: NotifLiveData<Int> = activeNotifCountPrivate
+    override val activeNotifList: NotifLiveData<List<NotificationEntry>> = activeNotifListPrivate
+
+    /** Set the latest flattened list of notification entries. */
+    fun setActiveNotifList(flatEntryList: List<NotificationEntry>) {
+        traceSection("NotifLiveDataStore.setActiveNotifList") {
+            Assert.isMainThread()
+            val unmodifiableCopy = unmodifiableList(flatEntryList.toList())
+            // This ensures we set all values before dispatching to any observers
+            listOf(
+                activeNotifListPrivate.setValueAndProvideDispatcher(unmodifiableCopy),
+                activeNotifCountPrivate.setValueAndProvideDispatcher(unmodifiableCopy.size),
+                hasActiveNotifsPrivate.setValueAndProvideDispatcher(unmodifiableCopy.isNotEmpty())
+            ).forEach { dispatcher -> dispatcher.invoke() }
+        }
+    }
+}
+
+/** Read-write implementation of [NotifLiveData] */
+class NotifLiveDataImpl<T>(
+    private val name: String,
+    initialValue: T,
+    @Main private val mainExecutor: Executor
+) : NotifLiveData<T> {
+    private val syncObservers = ListenerSet<Observer<T>>()
+    private val asyncObservers = ListenerSet<Observer<T>>()
+    private val atomicValue = AtomicReference(initialValue)
+    private var lastAsyncValue: T? = null
+
+    private fun dispatchToAsyncObservers() {
+        val value = atomicValue.get()
+        if (lastAsyncValue != value) {
+            lastAsyncValue = value
+            traceSection("NotifLiveData($name).dispatchToAsyncObservers") {
+                asyncObservers.forEach { it.onChanged(value) }
+            }
+        }
+    }
+
+    /**
+     * Access or set the current value.
+     *
+     * When setting, sync observers will be dispatched synchronously, and a task will be posted to
+     * dispatch the value to async observers.
+     */
+    override var value: T
+        get() = atomicValue.get()
+        set(value) = setValueAndProvideDispatcher(value).invoke()
+
+    /**
+     * Set the value, and return a function that when invoked will dispatch to the observers.
+     *
+     * This is intended to allow multiple instances with related data to be updated together and
+     * have their dispatchers invoked after all data has been updated.
+     */
+    fun setValueAndProvideDispatcher(value: T): () -> Unit {
+        val oldValue = atomicValue.getAndSet(value)
+        if (oldValue != value) {
+            return {
+                if (syncObservers.isNotEmpty()) {
+                    traceSection("NotifLiveData($name).dispatchToSyncObservers") {
+                        syncObservers.forEach { it.onChanged(value) }
+                    }
+                }
+                if (asyncObservers.isNotEmpty()) {
+                    mainExecutor.execute(::dispatchToAsyncObservers)
+                }
+            }
+        }
+        return {}
+    }
+
+    override fun addSyncObserver(observer: Observer<T>) {
+        syncObservers.addIfAbsent(observer)
+    }
+
+    override fun addAsyncObserver(observer: Observer<T>) {
+        asyncObservers.addIfAbsent(observer)
+    }
+
+    override fun removeObserver(observer: Observer<T>) {
+        syncObservers.remove(observer)
+        asyncObservers.remove(observer)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index 6fbed9a8..5ada7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -245,36 +245,4 @@
     fun getInternalNotifUpdater(name: String?): InternalNotifUpdater {
         return mNotifCollection.getInternalNotifUpdater(name)
     }
-
-    /**
-     * Returns a read-only view in to the current shade list, i.e. the list of notifications that
-     * are currently present in the shade.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    val shadeList: List<ListEntry>
-        get() = mShadeListBuilder.shadeList
-
-    /**
-     * Constructs a flattened representation of the notification tree, where each group will have
-     * the summary (if present) followed by the children.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry ->
-        when (entry) {
-            is NotificationEntry -> sequenceOf(entry)
-            is GroupEntry -> sequenceOf(entry.summary).filterNotNull() + entry.children
-            else -> throw RuntimeException("Unexpected entry $entry")
-        }
-    }
-
-    /**
-     * Returns the number of notifications currently shown in the shade. This includes all
-     * children and summary notifications.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    fun getShadeListCount(): Int = shadeList.sumOf { entry ->
-        // include the summary in the count
-        if (entry is GroupEntry) 1 + entry.children.size
-        else 1
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
new file mode 100644
index 0000000..8e307ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.render.requireSummary
+import javax.inject.Inject
+
+/**
+ * A small coordinator which updates the notif stack (the view layer which holds notifications)
+ * with high-level data after the stack is populated with the final entries.
+ */
+@CoordinatorScope
+class DataStoreCoordinator @Inject internal constructor(
+    private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl
+) : Coordinator {
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) }
+    }
+
+    fun onAfterRenderList(entries: List<ListEntry>) {
+        val flatEntryList = flattenedEntryList(entries)
+        notifLiveDataStoreImpl.setActiveNotifList(flatEntryList)
+    }
+
+    private fun flattenedEntryList(entries: List<ListEntry>) =
+        mutableListOf<NotificationEntry>().also { list ->
+            entries.forEach { entry ->
+                when (entry) {
+                    is NotificationEntry -> list.add(entry)
+                    is GroupEntry -> {
+                        list.add(entry.requireSummary)
+                        list.addAll(entry.children)
+                    }
+                    else -> error("Unexpected entry $entry")
+                }
+            }
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index a16b565..02649ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -35,6 +35,7 @@
 class NotifCoordinatorsImpl @Inject constructor(
     dumpManager: DumpManager,
     notifPipelineFlags: NotifPipelineFlags,
+    dataStoreCoordinator: DataStoreCoordinator,
     hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
     hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
     keyguardCoordinator: KeyguardCoordinator,
@@ -44,10 +45,11 @@
     bubbleCoordinator: BubbleCoordinator,
     headsUpCoordinator: HeadsUpCoordinator,
     gutsCoordinator: GutsCoordinator,
+    communalCoordinator: CommunalCoordinator,
     conversationCoordinator: ConversationCoordinator,
-    preparationCoordinator: PreparationCoordinator,
     groupCountCoordinator: GroupCountCoordinator,
     mediaCoordinator: MediaCoordinator,
+    preparationCoordinator: PreparationCoordinator,
     remoteInputCoordinator: RemoteInputCoordinator,
     rowAppearanceCoordinator: RowAppearanceCoordinator,
     stackCoordinator: StackCoordinator,
@@ -55,7 +57,6 @@
     smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
     viewConfigCoordinator: ViewConfigCoordinator,
     visualStabilityCoordinator: VisualStabilityCoordinator,
-    communalCoordinator: CommunalCoordinator,
     sensitiveContentCoordinator: SensitiveContentCoordinator
 ) : NotifCoordinators {
 
@@ -67,6 +68,16 @@
      */
     init {
         dumpManager.registerDumpable(TAG, this)
+
+        // TODO(b/208866714): formalize the system by which some coordinators may be required by the
+        //  pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
+        //  glue between the pipeline and parts of SystemUI which depend on pipeline output via the
+        //  NotifLiveDataStore.
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
+            mCoordinators.add(dataStoreCoordinator)
+        }
+
+        // Attach normal coordinators.
         mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
@@ -74,6 +85,7 @@
         mCoordinators.add(appOpsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
+        mCoordinators.add(communalCoordinator)
         mCoordinators.add(conversationCoordinator)
         mCoordinators.add(groupCountCoordinator)
         mCoordinators.add(mediaCoordinator)
@@ -83,7 +95,6 @@
         mCoordinators.add(shadeEventCoordinator)
         mCoordinators.add(viewConfigCoordinator)
         mCoordinators.add(visualStabilityCoordinator)
-        mCoordinators.add(communalCoordinator)
         mCoordinators.add(sensitiveContentCoordinator)
         if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
             mCoordinators.add(smartspaceDedupingCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 38f11fc..c6a8a69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -19,8 +19,8 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import javax.inject.Inject
@@ -49,10 +49,12 @@
         var hasNonClearableSilentNotifs = false
         var hasClearableSilentNotifs = false
         entries.forEach {
-            val isSilent = it.section!!.bucket == BUCKET_SILENT
+            val section = checkNotNull(it.section) { "Null section for ${it.key}" }
+            val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" }
+            val isSilent = section.bucket == BUCKET_SILENT
             // NOTE: NotificationEntry.isClearable will internally check group children to ensure
             //  the group itself definitively clearable.
-            val isClearable = it.representativeEntry!!.isClearable
+            val isClearable = entry.isClearable
             when {
                 isSilent && isClearable -> hasClearableSilentNotifs = true
                 isSilent && !isClearable -> hasNonClearableSilentNotifs = true
@@ -60,13 +62,12 @@
                 !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
             }
         }
-        val stats = NotifStats(
+        return NotifStats(
             numActiveNotifs = entries.size,
             hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
             hasClearableAlertingNotifs = hasClearableAlertingNotifs,
             hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
             hasClearableSilentNotifs = hasClearableSilentNotifs
         )
-        return stats
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
deleted file mode 100644
index 5c70f32..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy
-
-import com.android.internal.statusbar.NotificationVisibility
-import com.android.systemui.statusbar.notification.NotificationEntryManager
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.logging.NotificationLogger
-import javax.inject.Inject
-
-/** Legacy pipeline implementation for getting [NotificationVisibility]. */
-class LegacyNotificationVisibilityProvider @Inject constructor(
-    private val notifEntryManager: NotificationEntryManager
-) : NotificationVisibilityProvider {
-    override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
-        val count: Int = notifEntryManager.activeNotificationsCount
-        val rank = entry.ranking.rank
-        val hasRow = entry.row != null
-        val location = NotificationLogger.getNotificationLocation(entry)
-        return NotificationVisibility.obtain(entry.key, rank, count, visible && hasRow, location)
-    }
-
-    override fun obtain(key: String, visible: Boolean): NotificationVisibility {
-        val entry: NotificationEntry? = notifEntryManager.getActiveNotificationUnfiltered(key)
-        val count: Int = notifEntryManager.activeNotificationsCount
-        val rank = entry?.ranking?.rank ?: -1
-        val hasRow = entry?.row != null
-        val location = NotificationLogger.getNotificationLocation(entry)
-        return NotificationVisibility.obtain(key, rank, count, visible && hasRow, location)
-    }
-
-    override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
-            NotificationLogger.getNotificationLocation(
-                    notifEntryManager.getActiveNotificationUnfiltered(key))
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
index 51de08d..6a1e36f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
@@ -14,20 +14,24 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.collection.render
+package com.android.systemui.statusbar.notification.collection.provider
 
 import com.android.internal.statusbar.NotificationVisibility
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import javax.inject.Inject
 
-/** New pipeline implementation for getting [NotificationVisibility]. */
+/** pipeline-agnostic implementation for getting [NotificationVisibility]. */
 class NotificationVisibilityProviderImpl @Inject constructor(
-    private val notifPipeline: NotifPipeline
+    private val notifDataStore: NotifLiveDataStore,
+    private val notifCollection: CommonNotifCollection
 ) : NotificationVisibilityProvider {
+
     override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
-        val count: Int = notifPipeline.getShadeListCount()
+        val count: Int = getCount()
         val rank = entry.ranking.rank
         val hasRow = entry.row != null
         val location = NotificationLogger.getNotificationLocation(entry)
@@ -35,9 +39,11 @@
     }
 
     override fun obtain(key: String, visible: Boolean): NotificationVisibility =
-        notifPipeline.getEntry(key)?.let { return obtain(it, visible) }
-            ?: NotificationVisibility.obtain(key, -1, notifPipeline.getShadeListCount(), false)
+        notifCollection.getEntry(key)?.let { return obtain(it, visible) }
+            ?: NotificationVisibility.obtain(key, -1, getCount(), false)
 
     override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
-            NotificationLogger.getNotificationLocation(notifPipeline.getEntry(key))
+        NotificationLogger.getNotificationLocation(notifCollection.getEntry(key))
+
+    private fun getCount() = notifDataStore.activeNotifCount.value
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index d25a2d3..f1cba34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -44,6 +44,8 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
@@ -52,12 +54,12 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -65,7 +67,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProviderImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -122,6 +123,7 @@
             LeakDetector leakDetector,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
+            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager) {
         return new NotificationEntryManager(
                 logger,
@@ -132,6 +134,7 @@
                 leakDetector,
                 fgsFeatureController,
                 statusBarService,
+                notifLiveDataStore,
                 dumpManager);
     }
 
@@ -212,6 +215,7 @@
             NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
             NotifPipelineFlags notifPipelineFlags,
+            NotifLiveDataStore notifLiveDataStore,
             NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -222,6 +226,7 @@
                 notificationListener,
                 uiBgExecutor,
                 notifPipelineFlags,
+                notifLiveDataStore,
                 visibilityProvider,
                 entryManager,
                 notifPipeline,
@@ -290,16 +295,10 @@
     /**
      * Provide the object which can be used to obtain NotificationVisibility objects.
      */
-    @Provides
+    @Binds
     @SysUISingleton
-    static NotificationVisibilityProvider provideNotificationVisibilityProvider(
-            NotifPipelineFlags notifPipelineFlags,
-            Lazy<NotificationVisibilityProviderImpl> newProvider,
-            Lazy<LegacyNotificationVisibilityProvider> legacyProvider) {
-        return notifPipelineFlags.isNewPipelineEnabled()
-                ? newProvider.get()
-                : legacyProvider.get();
-    }
+    NotificationVisibilityProvider provideNotificationVisibilityProvider(
+            NotificationVisibilityProviderImpl newProvider);
 
     /**
      * Provide the active implementation for presenting notifications.
@@ -356,4 +355,8 @@
     /** */
     @Binds
     NotifInflater bindNotifInflater(NotifInflaterImpl notifInflaterImpl);
+
+    /** */
+    @Binds
+    NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 212c342e..84f0955 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -28,12 +28,14 @@
 import com.android.systemui.statusbar.notification.NotificationClicker
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationListController
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
 import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -64,9 +66,11 @@
     private val notificationListener: NotificationListener,
     private val entryManager: NotificationEntryManager,
     private val legacyRanker: NotificationRankingManager,
+    private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
+    private val notifLiveDataStore: NotifLiveDataStore,
     private val targetSdkResolver: TargetSdkResolver,
-    private val newNotifPipeline: Lazy<NotifPipelineInitializer>,
+    private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
@@ -111,7 +115,7 @@
         animatedImageNotificationManager.bind()
 
         if (INITIALIZE_NEW_PIPELINE) {
-            newNotifPipeline.get().initialize(
+            newNotifPipelineInitializer.get().initialize(
                     notificationListener,
                     notificationRowBinder,
                     listContainer,
@@ -155,14 +159,10 @@
     }
 
     override fun resetUserExpandedStates() {
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            for (entry in notifPipeline.get().allNotifs) {
-                entry.resetUserExpansion()
-            }
-        } else {
-            for (entry in entryManager.visibleNotifications) {
-                entry.resetUserExpansion()
-            }
+        // TODO: this is a view thing that should be done through the views, but that means doing it
+        //  both when this event is fired and any time a row is attached.
+        for (entry in commonNotifCollection.get().allNotifs) {
+            entry.resetUserExpansion()
         }
     }
 
@@ -177,11 +177,7 @@
     }
 
     override fun getActiveNotificationsCount(): Int =
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            notifPipeline.get().getShadeListCount()
-        } else {
-            entryManager.activeNotificationsCount
-        }
+        notifLiveDataStore.activeNotifCount.value
 
     companion object {
         // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 52488da..9e8200b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -76,7 +77,7 @@
     // Dependencies:
     private final NotificationListenerService mNotificationListener;
     private final Executor mUiBgExecutor;
-    private final NotifPipelineFlags mNotifPipelineFlags;
+    private final NotifLiveDataStore mNotifLiveDataStore;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationEntryManager mEntryManager;
     private final NotifPipeline mNotifPipeline;
@@ -179,11 +180,7 @@
     };
 
     private List<NotificationEntry> getVisibleNotifications() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            return mNotifPipeline.getFlatShadeList();
-        } else {
-            return mEntryManager.getVisibleNotifications();
-        }
+        return mNotifLiveDataStore.getActiveNotifList().getValue();
     }
 
     /**
@@ -223,6 +220,7 @@
     public NotificationLogger(NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
             NotifPipelineFlags notifPipelineFlags,
+            NotifLiveDataStore notifLiveDataStore,
             NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -231,7 +229,7 @@
             NotificationPanelLogger notificationPanelLogger) {
         mNotificationListener = notificationListener;
         mUiBgExecutor = uiBgExecutor;
-        mNotifPipelineFlags = notifPipelineFlags;
+        mNotifLiveDataStore = notifLiveDataStore;
         mVisibilityProvider = visibilityProvider;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
@@ -242,7 +240,7 @@
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
 
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
             registerNewPipelineListener();
         } else {
             registerLegacyListener();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index aa3b3e1..ad1c232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -74,7 +74,7 @@
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
-    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
+    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 2;
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index c09cca1..c61510c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,7 +22,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
 import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowInsetsController.Appearance;
@@ -30,13 +29,12 @@
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
 
+import androidx.lifecycle.Observer;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.util.ViewController;
 
@@ -44,20 +42,21 @@
 import javax.inject.Named;
 
 /**
- * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
+ * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}
  * where status bar and navigation icons dim. In this mode, a notification dot appears
  * where the notification icons would appear if they would be shown outside of this mode.
  *
  * This controller shows and hides the notification dot in the status bar to indicate
- * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}.
+ * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
  */
 @StatusBarFragmentScope
 public class LightsOutNotifController extends ViewController<View> {
     private final CommandQueue mCommandQueue;
-    private final NotificationEntryManager mEntryManager;
+    private final NotifLiveDataStore mNotifDataStore;
     private final WindowManager mWindowManager;
+    private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView();
 
-    /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
+    /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
     @VisibleForTesting @Appearance int mAppearance;
 
     private int mDisplayId;
@@ -66,18 +65,18 @@
     LightsOutNotifController(
             @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView,
             WindowManager windowManager,
-            NotificationEntryManager entryManager,
+            NotifLiveDataStore notifDataStore,
             CommandQueue commandQueue) {
         super(lightsOutNotifView);
         mWindowManager = windowManager;
-        mEntryManager = entryManager;
+        mNotifDataStore = notifDataStore;
         mCommandQueue = commandQueue;
 
     }
 
     @Override
     protected void onViewDetached() {
-        mEntryManager.removeNotificationEntryListener(mEntryListener);
+        mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver);
         mCommandQueue.removeCallback(mCallback);
     }
 
@@ -87,14 +86,14 @@
         mView.setAlpha(0f);
 
         mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId();
-        mEntryManager.addNotificationEntryListener(mEntryListener);
+        mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver);
         mCommandQueue.addCallback(mCallback);
 
         updateLightsOutView();
     }
 
     private boolean hasActiveNotifications() {
-        return mEntryManager.hasActiveNotifications();
+        return mNotifDataStore.getHasActiveNotifs().getValue();
     }
 
     @VisibleForTesting
@@ -153,23 +152,4 @@
             updateLightsOutView();
         }
     };
-
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
-        // Cares about notifications post-filtering
-        @Override
-        public void onNotificationAdded(NotificationEntry entry) {
-            updateLightsOutView();
-        }
-
-        @Override
-        public void onPostEntryUpdated(NotificationEntry entry) {
-            updateLightsOutView();
-        }
-
-        @Override
-        public void onEntryRemoved(@Nullable NotificationEntry entry,
-                NotificationVisibility visibility, boolean removedByUser, int reason) {
-            updateLightsOutView();
-        }
-    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index a64e579..4e68b19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -3419,12 +3419,6 @@
                     mStatusBarStateController.setState(KEYGUARD);
                 }
                 return true;
-            case StatusBarState.SHADE:
-
-                // This gets called in the middle of the touch handling, where the state is still
-                // that we are tracking the panel. Collapse the panel after this is done.
-                mView.post(mPostCollapseRunnable);
-                return false;
             default:
                 return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 9af79a9..53bfd77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -933,7 +933,6 @@
 
     private void abortAnimations() {
         cancelHeightAnimator();
-        mView.removeCallbacks(mPostCollapseRunnable);
         mView.removeCallbacks(mFlingCollapseRunnable);
     }
 
@@ -1110,13 +1109,6 @@
         return onMiddleClicked();
     }
 
-    protected final Runnable mPostCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        }
-    };
-
     protected abstract boolean onMiddleClicked();
 
     protected abstract boolean isDozing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index ec7e93b..b9386bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -15,12 +15,14 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.content.res.Configuration
 import android.graphics.Point
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.UNFOLD_STATUS_BAR
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -35,9 +37,16 @@
     view: PhoneStatusBarView,
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
-    touchEventHandler: PhoneStatusBarView.TouchEventHandler
+    touchEventHandler: PhoneStatusBarView.TouchEventHandler,
+    private val configurationController: ConfigurationController
 ) : ViewController<PhoneStatusBarView>(view) {
 
+    private val configurationListener = object : ConfigurationController.ConfigurationListener {
+        override fun onConfigChanged(newConfig: Configuration?) {
+            mView.updateResources()
+        }
+    }
+
     override fun onViewAttached() {
         moveFromCenterAnimationController?.let { animationController ->
             val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
@@ -66,11 +75,13 @@
         }
 
         progressProvider?.setReadyToHandleTransition(true)
+        configurationController.addCallback(configurationListener)
     }
 
     override fun onViewDetached() {
         progressProvider?.setReadyToHandleTransition(false)
         moveFromCenterAnimationController?.onViewDetached()
+        configurationController.removeCallback(configurationListener)
     }
 
     init {
@@ -116,7 +127,8 @@
     class Factory @Inject constructor(
         private val unfoldComponent: Optional<SysUIUnfoldComponent>,
         @Named(UNFOLD_STATUS_BAR)
-        private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>
+        private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
+        private val configurationController: ConfigurationController
     ) {
         fun create(
             view: PhoneStatusBarView,
@@ -128,7 +140,8 @@
                 unfoldComponent.map {
                     it.getStatusBarMoveFromCenterAnimationController()
                 }.getOrNull(),
-                touchEventHandler
+                touchEventHandler,
+                configurationController
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index a54251a..b4fed2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -218,10 +218,6 @@
         return getStatusBar().getNotificationShadeWindowView();
     }
 
-    protected PhoneStatusBarView getStatusBarView() {
-        return (PhoneStatusBarView) getStatusBar().getStatusBarView();
-    }
-
     private NotificationPanelViewController getNotificationPanelViewController() {
         return getStatusBar().getPanelController();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index 1ad9fa6..f6e19bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -18,6 +18,7 @@
 
 import android.view.View
 import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
@@ -49,7 +50,7 @@
 
     private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
     // TODO(b/194178072) Handle RSSI hiding when multi carrier
-    private val iconManager: StatusBarIconController.IconManager
+    private val iconManager: StatusBarIconController.TintedIconManager
     private val qsCarrierGroupController: QSCarrierGroupController
     private var visible = false
         set(value) {
@@ -117,7 +118,9 @@
         batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
 
         val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons)
-        iconManager = StatusBarIconController.IconManager(iconContainer, featureFlags)
+        iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+        iconManager.setTint(Utils.getColorAttrDefaultColor(statusBar.context,
+                android.R.attr.textColorPrimary))
         qsCarrierGroupController = qsCarrierGroupControllerBuilder
                 .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group))
                 .build()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 0f0a2f0..6c0b717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2715,9 +2715,6 @@
             mStatusBarWindowController.refreshStatusBarHeight();
         }
 
-        if (mStatusBarView != null) {
-            mStatusBarView.updateResources();
-        }
         if (mNotificationPanelViewController != null) {
             mNotificationPanelViewController.updateResources();
         }
@@ -4192,7 +4189,7 @@
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
-                if (!mUserSetup && mStatusBarView != null) {
+                if (!mUserSetup) {
                     animateCollapseQuickSettings();
                 }
                 if (mNotificationPanelViewController != null) {
@@ -4307,7 +4304,7 @@
                     updateTheme();
                     mNavigationBarController.touchAutoDim(mDisplayId);
                     Trace.beginSection("StatusBar#updateKeyguardState");
-                    if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+                    if (mState == StatusBarState.KEYGUARD) {
                         mNotificationPanelViewController.cancelPendingPanelCollapse();
                     }
                     updateDozingState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index abb7449..b391de3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -529,9 +529,7 @@
             if (StatusBar.DEBUG_WINDOW_STATE) {
                 Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state));
             }
-            if (mStatusBar.getStatusBarView() != null
-                    && !showing
-                    && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+            if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
                 mNotificationPanelViewController.collapsePanel(
                             false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 863ce57..ff86d74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -660,14 +660,6 @@
 
     // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
 
-    private int getVisibleNotificationsCount() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            return mNotifPipeline.getShadeListCount();
-        } else {
-            return mEntryManager.getActiveNotificationsCount();
-        }
-    }
-
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 43264b6..8df7b45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -126,30 +126,7 @@
      * the device configuration changes, and the result will be used to resize this dialog window.
      */
     protected int getWidth() {
-        boolean isOnTablet =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
-        if (!isOnTablet) {
-            return ViewGroup.LayoutParams.MATCH_PARENT;
-        }
-
-        int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
-        if (flagValue == -1) {
-            // The width of bottom sheets (624dp).
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
-                    mContext.getResources().getDisplayMetrics()));
-        } else if (flagValue == -2) {
-            // The suggested small width for all dialogs (348dp)
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
-                    mContext.getResources().getDisplayMetrics()));
-        } else if (flagValue > 0) {
-            // Any given width.
-            return Math.round(
-                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
-                            mContext.getResources().getDisplayMetrics()));
-        } else {
-            // By default we use the same width as the notification shade in portrait mode (504dp).
-            return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
-        }
+        return getDefaultDialogWidth(mContext);
     }
 
     /**
@@ -157,7 +134,7 @@
      * the device configuration changes, and the result will be used to resize this dialog window.
      */
     protected int getHeight() {
-        return ViewGroup.LayoutParams.WRAP_CONTENT;
+        return getDefaultDialogHeight();
     }
 
     @Override
@@ -267,6 +244,45 @@
         dismissReceiver.register();
     }
 
+    /** Set an appropriate size to {@code dialog} depending on the current configuration. */
+    public static void setDialogSize(Dialog dialog) {
+        // We need to create the dialog first, otherwise the size will be overridden when it is
+        // created.
+        dialog.create();
+        dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
+                getDefaultDialogHeight());
+    }
+
+    private static int getDefaultDialogWidth(Context context) {
+        boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+        if (!isOnTablet) {
+            return ViewGroup.LayoutParams.MATCH_PARENT;
+        }
+
+        int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+        if (flagValue == -1) {
+            // The width of bottom sheets (624dp).
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+                    context.getResources().getDisplayMetrics()));
+        } else if (flagValue == -2) {
+            // The suggested small width for all dialogs (348dp)
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+                    context.getResources().getDisplayMetrics()));
+        } else if (flagValue > 0) {
+            // Any given width.
+            return Math.round(
+                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+                            context.getResources().getDisplayMetrics()));
+        } else {
+            // By default we use the same width as the notification shade in portrait mode (504dp).
+            return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+        }
+    }
+
+    private static int getDefaultDialogHeight() {
+        return ViewGroup.LayoutParams.WRAP_CONTENT;
+    }
+
     private static class DismissReceiver extends BroadcastReceiver {
         private static final IntentFilter INTENT_FILTER = new IntentFilter();
         static {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index cac19aa..79ee746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1166,7 +1166,7 @@
                     ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
                     : R.string.guest_exit_guest_dialog_title);
             setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(mGuestUserAutoCreated
@@ -1185,7 +1185,7 @@
             if (mFalsingManager.isFalseTap(penalty)) {
                 return;
             }
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
@@ -1203,7 +1203,7 @@
             super(context);
             setTitle(R.string.user_add_user_title);
             setMessage(context.getString(R.string.user_add_user_message_short));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(android.R.string.ok), this);
@@ -1217,7 +1217,7 @@
             if (mFalsingManager.isFalseTap(penalty)) {
                 return;
             }
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 mDialogLaunchAnimator.dismissStack(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 8ea7d62..fb6861d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.tv;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -38,6 +39,10 @@
 @SysUISingleton
 public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks {
 
+    private static final String ACTION_SHOW_PIP_MENU =
+            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+    private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
     private final CommandQueue mCommandQueue;
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -65,4 +70,9 @@
     public void startAssist(Bundle args) {
         mAssistManagerLazy.get().startAssist(args);
     }
+
+    @Override
+    public void showPictureInPictureMenu() {
+        mContext.sendBroadcast(new Intent(ACTION_SHOW_PIP_MENU), SYSTEMUI_PERMISSION);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
index 0f4193e9..4f20067 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -39,9 +39,17 @@
     fun remove(element: E): Boolean = listeners.remove(element)
 
     /**
+     * Determine if the listener set is empty
+     */
+    fun isEmpty(): Boolean = listeners.isEmpty()
+
+    /**
      * Returns an iterator over the listeners currently in the set.  Note that to ensure
      * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes
      * made to the set after the iterator is constructed.
      */
     override fun iterator(): Iterator<E> = listeners.iterator()
 }
+
+/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */
+fun <T> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty()
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index fc353a1..5ffd300 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -18,12 +18,17 @@
         <option name="test-file-name" value="SystemUITests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
+
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="framework-base-presubmit" />
     <option name="test-tag" value="SystemUITests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.systemui.tests" />
         <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="test-filter-dir" value="/data/data/com.android.systemui.tests" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
new file mode 100644
index 0000000..cb16bec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.Serializable
+import java.io.StringWriter
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FeatureFlagsDebugTest : SysuiTestCase() {
+    private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
+
+    @Mock private lateinit var mFlagManager: FlagManager
+    @Mock private lateinit var mMockContext: Context
+    @Mock private lateinit var mSecureSettings: SecureSettings
+    @Mock private lateinit var mResources: Resources
+    @Mock private lateinit var mDumpManager: DumpManager
+    private val mFlagMap = mutableMapOf<Int, Flag<*>>()
+    private lateinit var mBroadcastReceiver: BroadcastReceiver
+    private lateinit var mClearCacheAction: Consumer<Int>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFeatureFlagsDebug = FeatureFlagsDebug(
+            mFlagManager,
+            mMockContext,
+            mSecureSettings,
+            mResources,
+            mDumpManager,
+            { mFlagMap }
+        )
+        verify(mFlagManager).restartAction = any()
+        mBroadcastReceiver = withArgCaptor {
+            verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable())
+        }
+        mClearCacheAction = withArgCaptor {
+            verify(mFlagManager).clearCacheAction = capture()
+        }
+        whenever(mFlagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
+    }
+
+    @Test
+    fun testReadBooleanFlag() {
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(1, false))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse()
+    }
+
+    @Test
+    fun testReadResourceBooleanFlag() {
+        whenever(mResources.getBoolean(1001)).thenReturn(false)
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getBoolean(1003)).thenReturn(false)
+        whenever(mResources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
+        whenever(mResources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
+
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
+
+        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, 1003))).isTrue()
+
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, 1004))
+        }
+        // Test that resource is loaded (and validated) even when the setting is set.
+        //  This prevents developers from not noticing when they reference an invalid resource.
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, 1005))
+        }
+    }
+
+    @Test
+    fun testReadStringFlag() {
+        whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
+        whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "buz"))).isEqualTo("bar")
+    }
+
+    @Test
+    fun testReadResourceStringFlag() {
+        whenever(mResources.getString(1001)).thenReturn("")
+        whenever(mResources.getString(1002)).thenReturn("resource2")
+        whenever(mResources.getString(1003)).thenReturn("resource3")
+        whenever(mResources.getString(1004)).thenReturn(null)
+        whenever(mResources.getString(1005)).thenAnswer { throw NameNotFoundException() }
+        whenever(mResources.getString(1006)).thenAnswer { throw NameNotFoundException() }
+
+        whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
+        whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
+        whenever(mFlagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
+
+        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
+        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2")
+        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(3, 1003))).isEqualTo("override3")
+
+        Assert.assertThrows(NullPointerException::class.java) {
+            mFeatureFlagsDebug.getString(ResourceStringFlag(4, 1004))
+        }
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.getString(ResourceStringFlag(5, 1005))
+        }
+        // Test that resource is loaded (and validated) even when the setting is set.
+        //  This prevents developers from not noticing when they reference an invalid resource.
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.getString(ResourceStringFlag(6, 1005))
+        }
+    }
+
+    @Test
+    fun testBroadcastReceiverIgnoresInvalidData() {
+        addFlag(BooleanFlag(1, false))
+        addFlag(ResourceBooleanFlag(2, 1002))
+        addFlag(StringFlag(3, "flag3"))
+        addFlag(ResourceStringFlag(4, 1004))
+
+        mBroadcastReceiver.onReceive(mMockContext, null)
+        mBroadcastReceiver.onReceive(mMockContext, Intent())
+        mBroadcastReceiver.onReceive(mMockContext, Intent("invalid action"))
+        mBroadcastReceiver.onReceive(mMockContext, Intent(FlagManager.ACTION_SET_FLAG))
+        setByBroadcast(0, false)     // unknown id does nothing
+        setByBroadcast(1, "string")  // wrong type does nothing
+        setByBroadcast(2, 123)       // wrong type does nothing
+        setByBroadcast(3, false)     // wrong type does nothing
+        setByBroadcast(4, 123)       // wrong type does nothing
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+    }
+
+    @Test
+    fun testIntentWithIdButNoValueKeyClears() {
+        addFlag(BooleanFlag(1, false))
+
+        // trying to erase an id not in the map does noting
+        mBroadcastReceiver.onReceive(
+            mMockContext,
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0)
+        )
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+
+        // valid id with no value puts empty string in the setting
+        mBroadcastReceiver.onReceive(
+            mMockContext,
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
+        )
+        verifyPutData(1, "", numReads = 0)
+    }
+
+    @Test
+    fun testSetBooleanFlag() {
+        addFlag(BooleanFlag(1, false))
+        addFlag(BooleanFlag(2, false))
+        addFlag(ResourceBooleanFlag(3, 1003))
+        addFlag(ResourceBooleanFlag(4, 1004))
+
+        setByBroadcast(1, false)
+        verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
+
+        setByBroadcast(2, true)
+        verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}")
+
+        setByBroadcast(3, false)
+        verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}")
+
+        setByBroadcast(4, true)
+        verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}")
+    }
+
+    @Test
+    fun testSetStringFlag() {
+        addFlag(StringFlag(1, "flag1"))
+        addFlag(ResourceStringFlag(2, 1002))
+
+        setByBroadcast(1, "override1")
+        verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
+
+        setByBroadcast(2, "override2")
+        verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}")
+    }
+
+    @Test
+    fun testSetFlagClearsCache() {
+        val flag1 = addFlag(StringFlag(1, "flag1"))
+        whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
+
+        // gets the flag & cache it
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        verify(mFlagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
+
+        // hit the cache
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        verifyNoMoreInteractions(mFlagManager)
+
+        // set the flag
+        setByBroadcast(1, "new")
+        verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
+        whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("new")
+
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
+        verify(mFlagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer))
+    }
+
+    private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
+        inOrder(mFlagManager, mSecureSettings).apply {
+            verify(mFlagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
+            verify(mFlagManager).idToSettingsKey(eq(id))
+            verify(mSecureSettings).putString(eq("key-$id"), eq(data))
+            verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id))
+        }.verifyNoMoreInteractions()
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+    }
+
+    private fun setByBroadcast(id: Int, value: Serializable?) {
+        val intent = Intent(FlagManager.ACTION_SET_FLAG)
+        intent.putExtra(FlagManager.EXTRA_ID, id)
+        intent.putExtra(FlagManager.EXTRA_VALUE, value)
+        mBroadcastReceiver.onReceive(mMockContext, intent)
+    }
+
+    private fun <F : Flag<*>> addFlag(flag: F): F {
+        val old = mFlagMap.put(flag.id, flag)
+        check(old == null) { "Flag ${flag.id} already registered" }
+        return flag
+    }
+
+    @Test
+    fun testDump() {
+        val flag1 = BooleanFlag(1, true)
+        val flag2 = ResourceBooleanFlag(2, 1002)
+        val flag3 = BooleanFlag(3, false)
+        val flag4 = StringFlag(4, "")
+        val flag5 = StringFlag(5, "flag5default")
+        val flag6 = ResourceStringFlag(6, 1006)
+        val flag7 = ResourceStringFlag(7, 1007)
+
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getString(1006)).thenReturn("resource1006")
+        whenever(mResources.getString(1007)).thenReturn("resource1007")
+        whenever(mFlagManager.readFlagValue(eq(7), eq(StringFlagSerializer)))
+            .thenReturn("override7")
+
+        // WHEN the flags have been accessed
+        assertThat(mFeatureFlagsDebug.isEnabled(flag1)).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(flag2)).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(flag3)).isFalse()
+        assertThat(mFeatureFlagsDebug.getString(flag4)).isEmpty()
+        assertThat(mFeatureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
+        assertThat(mFeatureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
+        assertThat(mFeatureFlagsDebug.getString(flag7)).isEqualTo("override7")
+
+        // THEN the dump contains the flags and the default values
+        val dump = dumpToString()
+        assertThat(dump).contains(" sysui_flag_1: true\n")
+        assertThat(dump).contains(" sysui_flag_2: true\n")
+        assertThat(dump).contains(" sysui_flag_3: false\n")
+        assertThat(dump).contains(" sysui_flag_4: [length=0] \"\"\n")
+        assertThat(dump).contains(" sysui_flag_5: [length=12] \"flag5default\"\n")
+        assertThat(dump).contains(" sysui_flag_6: [length=12] \"resource1006\"\n")
+        assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
+    }
+
+    private fun dumpToString(): String {
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        mFeatureFlagsDebug.dump(mock(), pw, emptyArray<String>())
+        pw.flush()
+        return sw.toString()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
deleted file mode 100644
index dea42f9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2021 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.flags;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
- * overriding, and should never return any value other than the one provided as the default.
- */
-@SmallTest
-public class FeatureFlagsReleaseTest extends SysuiTestCase {
-    FeatureFlagsRelease mFeatureFlagsRelease;
-
-    @Mock private Resources mResources;
-    @Mock private DumpManager mDumpManager;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mFeatureFlagsRelease = new FeatureFlagsRelease(mResources, mDumpManager);
-    }
-
-    @After
-    public void onFinished() {
-        // The dump manager should be registered with even for the release version, but that's it.
-        verify(mDumpManager).registerDumpable(anyString(), any());
-        verifyNoMoreInteractions(mDumpManager);
-    }
-
-    @Test
-    public void testBooleanResourceFlag() {
-        int flagId = 213;
-        int flagResourceId = 3;
-        ResourceBooleanFlag flag = new ResourceBooleanFlag(flagId, flagResourceId);
-        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
-
-        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue();
-    }
-
-    @Test
-    public void testDump() {
-        int flagIdA = 213;
-        int flagIdB = 18;
-        int flagResourceId = 3;
-        BooleanFlag flagA = new BooleanFlag(flagIdA, true);
-        ResourceBooleanFlag flagB = new ResourceBooleanFlag(flagIdB, flagResourceId);
-        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
-
-        // WHEN the flags have been accessed
-        assertThat(mFeatureFlagsRelease.isEnabled(1, false)).isFalse();
-        assertThat(mFeatureFlagsRelease.isEnabled(flagA)).isTrue();
-        assertThat(mFeatureFlagsRelease.isEnabled(flagB)).isTrue();
-
-        // THEN the dump contains the flags and the default values
-        String dump = dumpToString();
-        assertThat(dump).contains(" sysui_flag_1: false\n");
-        assertThat(dump).contains(" sysui_flag_" + flagIdA + ": true\n");
-        assertThat(dump).contains(" sysui_flag_" + flagIdB + ": true\n");
-    }
-
-    private String dumpToString() {
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        mFeatureFlagsRelease.dump(mock(FileDescriptor.class), pw, new String[0]);
-        pw.flush();
-        String dump = sw.toString();
-        return dump;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
new file mode 100644
index 0000000..b5e6602
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FeatureFlagsReleaseTest : SysuiTestCase() {
+    private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease
+
+    @Mock private lateinit var mResources: Resources
+    @Mock private lateinit var mDumpManager: DumpManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mDumpManager)
+    }
+
+    @After
+    fun onFinished() {
+        // The dump manager should be registered with even for the release version, but that's it.
+        verify(mDumpManager).registerDumpable(any(), any())
+        verifyNoMoreInteractions(mDumpManager)
+    }
+
+    @Test
+    fun testBooleanResourceFlag() {
+        val flagId = 213
+        val flagResourceId = 3
+        val flag = ResourceBooleanFlag(flagId, flagResourceId)
+        whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
+        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
+    }
+
+    @Test
+    fun testReadResourceStringFlag() {
+        whenever(mResources.getString(1001)).thenReturn("")
+        whenever(mResources.getString(1002)).thenReturn("res2")
+        whenever(mResources.getString(1003)).thenReturn(null)
+        whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
+
+        assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
+        assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(2, 1002))).isEqualTo("res2")
+
+        assertThrows(NullPointerException::class.java) {
+            mFeatureFlagsRelease.getString(ResourceStringFlag(3, 1003))
+        }
+        assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsRelease.getString(ResourceStringFlag(4, 1004))
+        }
+    }
+
+    @Test
+    fun testDump() {
+        val flag1 = BooleanFlag(1, true)
+        val flag2 = ResourceBooleanFlag(2, 1002)
+        val flag3 = BooleanFlag(3, false)
+        val flag4 = StringFlag(4, "")
+        val flag5 = StringFlag(5, "flag5default")
+        val flag6 = ResourceStringFlag(6, 1006)
+
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getString(1006)).thenReturn("resource1006")
+        whenever(mResources.getString(1007)).thenReturn("resource1007")
+
+        // WHEN the flags have been accessed
+        assertThat(mFeatureFlagsRelease.isEnabled(flag1)).isTrue()
+        assertThat(mFeatureFlagsRelease.isEnabled(flag2)).isTrue()
+        assertThat(mFeatureFlagsRelease.isEnabled(flag3)).isFalse()
+        assertThat(mFeatureFlagsRelease.getString(flag4)).isEmpty()
+        assertThat(mFeatureFlagsRelease.getString(flag5)).isEqualTo("flag5default")
+        assertThat(mFeatureFlagsRelease.getString(flag6)).isEqualTo("resource1006")
+
+        // THEN the dump contains the flags and the default values
+        val dump = dumpToString()
+        assertThat(dump).contains(" sysui_flag_1: true\n")
+        assertThat(dump).contains(" sysui_flag_2: true\n")
+        assertThat(dump).contains(" sysui_flag_3: false\n")
+        assertThat(dump).contains(" sysui_flag_4: [length=0] \"\"\n")
+        assertThat(dump).contains(" sysui_flag_5: [length=12] \"flag5default\"\n")
+        assertThat(dump).contains(" sysui_flag_6: [length=12] \"resource1006\"\n")
+    }
+
+    private fun dumpToString(): String {
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        mFeatureFlagsRelease.dump(mock(), pw, emptyArray())
+        pw.flush()
+        return sw.toString()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
new file mode 100644
index 0000000..644bd21
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FlagManagerTest : SysuiTestCase() {
+    private lateinit var mFlagManager: FlagManager
+
+    @Mock private lateinit var mMockContext: Context
+    @Mock private lateinit var mFlagSettingsHelper: FlagSettingsHelper
+    @Mock private lateinit var mHandler: Handler
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFlagManager = FlagManager(mMockContext, mFlagSettingsHelper, mHandler)
+    }
+
+    @Test
+    fun testContentObserverAddedAndRemoved() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener2 = mock<FlagListenable.Listener>()
+
+        // no interactions before adding listener
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // adding the first listener registers the observer
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // adding another listener does nothing
+        mFlagManager.addListener(BooleanFlag(2, true), listener2)
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // removing the original listener does nothing with second one still present
+        mFlagManager.removeListener(listener1)
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // removing the final listener unregisters the observer
+        mFlagManager.removeListener(listener2)
+        verify(mFlagSettingsHelper).unregisterContentObserver(eq(observer))
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+    }
+
+    @Test
+    fun testObserverClearsCache() {
+        val listener = mock<FlagListenable.Listener>()
+        val clearCacheAction = mock<Consumer<Int>>()
+        mFlagManager.clearCacheAction = clearCacheAction
+        mFlagManager.addListener(BooleanFlag(1, true), listener)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        observer.onChange(false, flagUri(1))
+        verify(clearCacheAction).accept(eq(1))
+    }
+
+    @Test
+    fun testObserverInvokesListeners() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener10 = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        mFlagManager.addListener(BooleanFlag(10, true), listener10)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        observer.onChange(false, flagUri(1))
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener1).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener1, listener10)
+
+        observer.onChange(false, flagUri(10))
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener10).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener1, listener10)
+    }
+
+    fun flagUri(id: Int): Uri = Uri.parse("content://settings/system/systemui/flags/$id")
+
+    @Test
+    fun testOnlySpecificFlagListenerIsInvoked() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener10 = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        mFlagManager.addListener(BooleanFlag(10, true), listener10)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener1).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener1, listener10)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener10).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener1, listener10)
+    }
+
+    @Test
+    fun testSameListenerCanBeUsedForMultipleFlags() {
+        val listener = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener)
+        mFlagManager.addListener(BooleanFlag(10, true), listener)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener, times(2)).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener)
+    }
+
+    @Test
+    fun testRestartWithNoListeners() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testListenerCanSuppressRestart() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(1, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(true))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testListenerOnlySuppressesRestartForOwnFlag() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(10, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testRestartWhenNotAllListenersRequestSuppress() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(10, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.addListener(BooleanFlag(10, true)) {
+            // do not request
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testReadBooleanFlag() {
+        // test that null string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // test that empty string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // test false
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":false}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
+
+        // test true
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":true}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
+
+        // Reading a value of a different type should just return null
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // Reading a value that isn't json should throw an exception
+        assertThrows(InvalidFlagStorageException::class.java) {
+            whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
+            mFlagManager.readFlagValue(1, BooleanFlagSerializer)
+        }
+    }
+
+    @Test
+    fun testSerializeBooleanFlag() {
+        // test false
+        assertThat(BooleanFlagSerializer.toSettingsData(false))
+            .isEqualTo("{\"type\":\"boolean\",\"value\":false}")
+
+        // test true
+        assertThat(BooleanFlagSerializer.toSettingsData(true))
+            .isEqualTo("{\"type\":\"boolean\",\"value\":true}")
+    }
+
+    @Test
+    fun testReadStringFlag() {
+        // test that null string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // test that empty string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // test json with the empty string value returns empty string
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"\"}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
+
+        // test string with value is returned
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
+
+        // Reading a value of a different type should just return null
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":false}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // Reading a value that isn't json should throw an exception
+        assertThrows(InvalidFlagStorageException::class.java) {
+            whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
+            mFlagManager.readFlagValue(1, StringFlagSerializer)
+        }
+    }
+
+    @Test
+    fun testSerializeStringFlag() {
+        // test empty string
+        assertThat(StringFlagSerializer.toSettingsData(""))
+            .isEqualTo("{\"type\":\"string\",\"value\":\"\"}")
+
+        // test string "foo"
+        assertThat(StringFlagSerializer.toSettingsData("foo"))
+            .isEqualTo("{\"type\":\"string\",\"value\":\"foo\"}")
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 6dca2a7..47f6e5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -235,10 +235,18 @@
     }
 
     @Test
-    public void getSubtitleText_withAirplaneModeOn_returnNull() {
+    public void getSubtitleText_withApmOnAndWifiOff_returnWifiIsOff() {
         fakeAirplaneModeEnabled(true);
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
-        assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("wifi_is_off"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("wifi_is_off"));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index f62de51..dc83c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -201,6 +202,7 @@
                 mLeakDetector,
                 mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
+                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
         );
         mEntryManager.initialize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 2971c05..b45d78d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -18,6 +18,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -28,6 +30,7 @@
     private String mKey = "test_group_key";
     private long mCreationTime = 0;
     @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
+    private NotifSection mNotifSection;
     private NotificationEntry mSummary = null;
     private List<NotificationEntry> mChildren = new ArrayList<>();
 
@@ -35,6 +38,7 @@
     public GroupEntry build() {
         GroupEntry ge = new GroupEntry(mKey, mCreationTime);
         ge.setParent(mParent);
+        ge.getAttachState().setSection(mNotifSection);
 
         ge.setSummary(mSummary);
         mSummary.setParent(ge);
@@ -61,6 +65,11 @@
         return this;
     }
 
+    public GroupEntryBuilder setSection(@Nullable NotifSection section) {
+        mNotifSection = section;
+        return this;
+    }
+
     public GroupEntryBuilder setSummary(
             NotificationEntry summary) {
         mSummary = summary;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
new file mode 100644
index 0000000..892575a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLiveDataImplTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val liveDataImpl: NotifLiveDataImpl<Int> = NotifLiveDataImpl("tst", 9, executor)
+    private val syncObserver: Observer<Int> = mock()
+    private val asyncObserver: Observer<Int> = mock()
+
+    @Before
+    fun setup() {
+        allowTestableLooperAsMainThread()
+        liveDataImpl.addSyncObserver(syncObserver)
+        liveDataImpl.addAsyncObserver(asyncObserver)
+    }
+
+    @Test
+    fun testGetInitialValue() {
+        assertThat(liveDataImpl.value).isEqualTo(9)
+    }
+
+    @Test
+    fun testGetModifiedValue() {
+        liveDataImpl.value = 13
+        assertThat(liveDataImpl.value).isEqualTo(13)
+    }
+
+    @Test
+    fun testGetsModifiedValueFromWithinSyncObserver() {
+        liveDataImpl.addSyncObserver { intVal ->
+            assertThat(intVal).isEqualTo(13)
+            assertThat(liveDataImpl.value).isEqualTo(13)
+        }
+        liveDataImpl.value = 13
+    }
+
+    @Test
+    fun testDoesNotAlertsRemovedObservers() {
+        liveDataImpl.removeObserver(syncObserver)
+        liveDataImpl.removeObserver(asyncObserver)
+
+        liveDataImpl.value = 13
+
+        // There should be no runnables on the executor
+        assertThat(executor.runAllReady()).isEqualTo(0)
+
+        // And observers should not be called
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testDoesNotAsyncObserversRemovedSinceChange() {
+        liveDataImpl.value = 13
+        liveDataImpl.removeObserver(asyncObserver)
+
+        // There should be a runnable that will get executed...
+        assertThat(executor.runAllReady()).isEqualTo(1)
+
+        // ...but async observers should not be called
+        verifyNoMoreInteractions(asyncObserver)
+    }
+
+    @Test
+    fun testAlertsObservers() {
+        liveDataImpl.value = 13
+
+        // Verify that the synchronous observer is called immediately
+        verify(syncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Verify that the asynchronous observer is called when the executor runs
+        assertThat(executor.runAllReady()).isEqualTo(1)
+        verify(asyncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testAlertsObserversFromDispatcher() {
+        // GIVEN that we use setValueAndProvideDispatcher()
+        val dispatcher = liveDataImpl.setValueAndProvideDispatcher(13)
+
+        // VERIFY that nothing is done before the dispatcher is called
+        assertThat(executor.numPending()).isEqualTo(0)
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // WHEN the dispatcher is invoked...
+        dispatcher.invoke()
+
+        // Verify that the synchronous observer is called immediately
+        verify(syncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Verify that the asynchronous observer is called when the executor runs
+        assertThat(executor.runAllReady()).isEqualTo(1)
+        verify(asyncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testSkipsAllObserversIfValueDidNotChange() {
+        liveDataImpl.value = 9
+        // Does not add a runnable
+        assertThat(executor.runAllReady()).isEqualTo(0)
+        // Setting the current value does not call synchronous observers
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testSkipsAsyncObserversWhenValueTogglesBack() {
+        liveDataImpl.value = 13
+        liveDataImpl.value = 11
+        liveDataImpl.value = 9
+
+        // Synchronous observers will receive every change event immediately
+        inOrder(syncObserver).apply {
+            verify(syncObserver).onChanged(eq(13))
+            verify(syncObserver).onChanged(eq(11))
+            verify(syncObserver).onChanged(eq(9))
+        }
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Running the first runnable on the queue will just emit the most recent value
+        assertThat(executor.runNextReady()).isTrue()
+        verify(asyncObserver).onChanged(eq(9))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Running the next 2 runnable will have no effect
+        assertThat(executor.runAllReady()).isEqualTo(2)
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
new file mode 100644
index 0000000..9c8ac5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.UnsupportedOperationException
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLiveDataStoreImplTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val liveDataStoreImpl = NotifLiveDataStoreImpl(executor)
+
+    @Before
+    fun setup() {
+        allowTestableLooperAsMainThread()
+    }
+
+    @Test
+    fun testAllObserversSeeConsistentValues() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val observer: (Any) -> Unit = {
+            assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true)
+            assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2)
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer)
+        liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(listOf(entry1, entry2))
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testOriginalListIsCopied() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val mutableInputList = mutableListOf(entry1, entry2)
+        val observer: (Any) -> Unit = {
+            mutableInputList.clear()
+            assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true)
+            assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2)
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer)
+        liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(mutableInputList)
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testProvidedListIsUnmodifiable() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val observer: (List<NotificationEntry>) -> Unit = { providedValue ->
+            val provided = providedValue as MutableList<NotificationEntry>
+            Assert.assertThrows(UnsupportedOperationException::class.java) {
+                provided.clear()
+            }
+            val current = liveDataStoreImpl.activeNotifList.value as MutableList<NotificationEntry>
+            Assert.assertThrows(UnsupportedOperationException::class.java) {
+                current.clear()
+            }
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2))
+        executor.runAllReady()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
new file mode 100644
index 0000000..6e81c69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import com.android.systemui.util.mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+/** Creates a mock which returns mocks for the NotifLiveDataImpl fields. */
+fun createNotifLiveDataStoreImplMock(): NotifLiveDataStoreImpl {
+    val dataStoreImpl: NotifLiveDataStoreImpl = mock()
+    whenever(dataStoreImpl.hasActiveNotifs).thenReturn(mock())
+    whenever(dataStoreImpl.activeNotifCount).thenReturn(mock())
+    whenever(dataStoreImpl.activeNotifList).thenReturn(mock())
+    return dataStoreImpl
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
deleted file mode 100644
index 287f50c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class NotifPipelineTest : SysuiTestCase() {
-
-    @Mock private lateinit var notifCollection: NotifCollection
-    @Mock private lateinit var shadeListBuilder: ShadeListBuilder
-    @Mock private lateinit var renderStageManager: RenderStageManager
-    private lateinit var notifPipeline: NotifPipeline
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager)
-        whenever(shadeListBuilder.shadeList).thenReturn(listOf(
-                NotificationEntryBuilder().setPkg("foo").setId(1).build(),
-                NotificationEntryBuilder().setPkg("foo").setId(2).build(),
-                group(
-                        NotificationEntryBuilder().setPkg("bar").setId(1).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(2).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(3).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(4).build()
-                ),
-                NotificationEntryBuilder().setPkg("baz").setId(1).build()
-        ))
-    }
-
-    private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry {
-        return GroupEntry(summary.key, summary.creationTime).also { group ->
-            group.summary = summary
-            for (it in children) {
-                group.addChild(it)
-            }
-        }
-    }
-
-    @Test
-    fun testGetShadeListCount() {
-        assertThat(notifPipeline.getShadeListCount()).isEqualTo(7)
-    }
-
-    @Test
-    fun testGetFlatShadeList() {
-        assertThat(notifPipeline.getFlatShadeList().map { it.key }).containsExactly(
-                "0|foo|1|null|0",
-                "0|foo|2|null|0",
-                "0|bar|1|null|0",
-                "0|bar|2|null|0",
-                "0|bar|3|null|0",
-                "0|bar|4|null|0",
-                "0|baz|1|null|0"
-        ).inOrder()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
new file mode 100644
index 0000000..59fc591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class DataStoreCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: DataStoreCoordinator
+    private lateinit var afterRenderListListener: OnAfterRenderListListener
+
+    private lateinit var entry: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var notifLiveDataStoreImpl: NotifLiveDataStoreImpl
+    @Mock private lateinit var stackController: NotifStackController
+    @Mock private lateinit var section: NotifSection
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        entry = NotificationEntryBuilder().setSection(section).build()
+        coordinator = DataStoreCoordinator(notifLiveDataStoreImpl)
+        coordinator.attach(pipeline)
+        afterRenderListListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderListListener(capture())
+        }
+    }
+
+    @Test
+    fun testUpdateDataStore_withOneEntry() {
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf(entry)))
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+
+    @Test
+    fun testUpdateDataStore_withGroups() {
+        afterRenderListListener.onAfterRenderList(
+            listOf(
+                notificationEntry("foo", 1),
+                notificationEntry("foo", 2),
+                GroupEntryBuilder().setSummary(
+                    notificationEntry("bar", 1)
+                ).setChildren(
+                    listOf(
+                        notificationEntry("bar", 2),
+                        notificationEntry("bar", 3),
+                        notificationEntry("bar", 4)
+                    )
+                ).setSection(section).build(),
+                notificationEntry("baz", 1)
+            ),
+            stackController
+        )
+        val list: List<NotificationEntry> = withArgCaptor {
+            verify(notifLiveDataStoreImpl).setActiveNotifList(capture())
+        }
+        assertThat(list.map { it.key }).containsExactly(
+            "0|foo|1|null|0",
+            "0|foo|2|null|0",
+            "0|bar|1|null|0",
+            "0|bar|2|null|0",
+            "0|bar|3|null|0",
+            "0|bar|4|null|0",
+            "0|baz|1|null|0"
+        ).inOrder()
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+
+    private fun notificationEntry(pkg: String, id: Int) =
+        NotificationEntryBuilder().setPkg(pkg).setId(id).setSection(section).build()
+
+    @Test
+    fun testUpdateDataStore_withZeroEntries_whenNewPipelineEnabled() {
+        afterRenderListListener.onAfterRenderList(listOf(), stackController)
+        verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf()))
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
index 395aec3..2dfb9fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,6 +67,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 
@@ -82,6 +85,8 @@
 
     // Dependency mocks:
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
+    @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifList;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotifPipeline mNotifPipeline;
@@ -97,6 +102,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifList);
 
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -112,6 +118,7 @@
                 mListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mEntryManager,
                 mNotifPipeline,
@@ -145,7 +152,7 @@
                 any(NotificationVisibility[].class));
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -167,7 +174,7 @@
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -196,7 +203,7 @@
 
     @Test
     public void testLogPanelShownOnWake() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -212,7 +219,7 @@
 
     @Test
     public void testLogPanelShownOnShadePull() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
         mLogger.onPanelExpandedChanged(true);
@@ -240,7 +247,7 @@
                 .build();
         entry.setRow(mRow);
 
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -254,6 +261,7 @@
         TestableNotificationLogger(NotificationListener notificationListener,
                 Executor uiBgExecutor,
                 NotifPipelineFlags notifPipelineFlags,
+                NotifLiveDataStore notifLiveDataStore,
                 NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -264,6 +272,7 @@
                     notificationListener,
                     uiBgExecutor,
                     notifPipelineFlags,
+                    notifLiveDataStore,
                     visibilityProvider,
                     entryManager,
                     notifPipeline,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 3a9b297..4d861f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,6 +67,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 
@@ -82,6 +85,8 @@
 
     // Dependency mocks:
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
+    @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifEntries;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotifPipeline mNotifPipeline;
@@ -98,6 +103,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
+        when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
 
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -113,6 +119,7 @@
                 mListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mEntryManager,
                 mNotifPipeline,
@@ -146,7 +153,7 @@
                 any(NotificationVisibility[].class));
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -168,7 +175,7 @@
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -197,7 +204,7 @@
 
     @Test
     public void testLogPanelShownOnWake() {
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -213,7 +220,7 @@
 
     @Test
     public void testLogPanelShownOnShadePull() {
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
         mLogger.onPanelExpandedChanged(true);
@@ -241,7 +248,7 @@
                 .build();
         entry.setRow(mRow);
 
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(entry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -255,6 +262,7 @@
         TestableNotificationLogger(NotificationListener notificationListener,
                 Executor uiBgExecutor,
                 NotifPipelineFlags notifPipelineFlags,
+                NotifLiveDataStore notifLiveDataStore,
                 NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -265,6 +273,7 @@
                     notificationListener,
                     uiBgExecutor,
                     notifPipelineFlags,
+                    notifLiveDataStore,
                     visibilityProvider,
                     entryManager,
                     notifPipeline,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index eeda9dd..a890414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -191,6 +192,7 @@
                 mLeakDetector,
                 mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
+                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
         );
         mEntryManager.initialize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 07debe6..c3349f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -381,16 +381,15 @@
     }
 
     @Test
-    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+    public void onUdfpsConsecutivelyFailedTwoTimes_showBouncer() {
         // GIVEN UDFPS is supported
         when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
 
-        // WHEN udfps fails twice - then don't show the bouncer
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        // WHEN udfps fails once - then don't show the bouncer
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
 
-        // WHEN udfps fails the third time
+        // WHEN udfps fails the second time
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN show the bouncer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index e386263..9664035 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -16,14 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,13 +32,13 @@
 import android.view.ViewPropertyAnimator;
 import android.view.WindowManager;
 
+import androidx.lifecycle.Observer;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,18 +57,19 @@
     private static final int LIGHTS_ON = 0;
     private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS;
 
-    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotifLiveData<Boolean> mHasActiveNotifs;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
     @Mock private CommandQueue mCommandQueue;
     @Mock private WindowManager mWindowManager;
     @Mock private Display mDisplay;
 
-    @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
+    @Captor private ArgumentCaptor<Observer<Boolean>> mObserverCaptor;
     @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor;
 
     private View mLightsOutView;
     private LightsOutNotifController mLightsOutNotifController;
     private int mDisplayId;
-    private NotificationEntryListener mEntryListener;
+    private Observer<Boolean> mHaActiveNotifsObserver;
     private CommandQueue.Callbacks mCallbacks;
 
     @Before
@@ -80,15 +79,20 @@
         mLightsOutView = new View(mContext);
         when(mWindowManager.getDefaultDisplay()).thenReturn(mDisplay);
         when(mDisplay.getDisplayId()).thenReturn(mDisplayId);
+        when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
 
         mLightsOutNotifController = new LightsOutNotifController(
-                mLightsOutView, mWindowManager, mEntryManager, mCommandQueue);
+                mLightsOutView,
+                mWindowManager,
+                mNotifLiveDataStore,
+                mCommandQueue);
         mLightsOutNotifController.init();
         mLightsOutNotifController.onViewAttached();
 
         // Capture the entry listener object so we can simulate events in tests below
-        verify(mEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
-        mEntryListener = Objects.requireNonNull(mListenerCaptor.getValue());
+        verify(mHasActiveNotifs).addSyncObserver(mObserverCaptor.capture());
+        mHaActiveNotifsObserver = Objects.requireNonNull(mObserverCaptor.getValue());
 
         // Capture the callback object so we can simulate callback events in tests below
         verify(mCommandQueue).addCallback(mCallbacksCaptor.capture());
@@ -138,7 +142,7 @@
     @Test
     public void testLightsOut_withNotifs_onSystemBarAttributesChanged() {
         // GIVEN active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
 
         // WHEN lights out
         mCallbacks.onSystemBarAttributesChanged(
@@ -158,7 +162,7 @@
     @Test
     public void testLightsOut_withoutNotifs_onSystemBarAttributesChanged() {
         // GIVEN no active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
 
         // WHEN lights out
         mCallbacks.onSystemBarAttributesChanged(
@@ -178,7 +182,7 @@
     @Test
     public void testLightsOn_afterLightsOut_onSystemBarAttributesChanged() {
         // GIVEN active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
 
         // WHEN lights on
         mCallbacks.onSystemBarAttributesChanged(
@@ -198,15 +202,15 @@
     @Test
     public void testEntryAdded() {
         // GIVEN no visible notifications and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
         mLightsOutNotifController.mAppearance = LIGHTS_OUT;
         mLightsOutNotifController.updateLightsOutView();
         assertIsShowingDot(false);
 
         // WHEN an active notification is added
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
         assertTrue(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onNotificationAdded(mock(NotificationEntry.class));
+        mHaActiveNotifsObserver.onChanged(true);
 
         // THEN we should see the dot view
         assertIsShowingDot(true);
@@ -215,38 +219,20 @@
     @Test
     public void testEntryRemoved() {
         // GIVEN a visible notification and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
         mLightsOutNotifController.mAppearance = LIGHTS_OUT;
         mLightsOutNotifController.updateLightsOutView();
         assertIsShowingDot(true);
 
         // WHEN all active notifications are removed
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
         assertFalse(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onEntryRemoved(
-                mock(NotificationEntry.class), null, false, REASON_CANCEL_ALL);
+        mHaActiveNotifsObserver.onChanged(false);
 
         // THEN we shouldn't see the dot view
         assertIsShowingDot(false);
     }
 
-    @Test
-    public void testEntryUpdated() {
-        // GIVEN no visible notifications and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
-        mLightsOutNotifController.mAppearance = LIGHTS_OUT;
-        mLightsOutNotifController.updateLightsOutView();
-        assertIsShowingDot(false);
-
-        // WHEN an active notification is added
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
-        assertTrue(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onPostEntryUpdated(mock(NotificationEntry.class));
-
-        // THEN we should see the dot view
-        assertIsShowingDot(true);
-    }
-
     private void assertIsShowingDot(boolean isShowing) {
         // cancel the animation so we can check the end state
         final ViewPropertyAnimator animation = mLightsOutView.animate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 7d266e9..235de1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -57,6 +58,8 @@
     private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
     @Mock
     private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider
+    @Mock
+    private lateinit var configurationController: ConfigurationController
 
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
@@ -116,7 +119,8 @@
     private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
             Optional.of(sysuiUnfoldComponent),
-            Optional.of(progressProvider)
+            Optional.of(progressProvider),
+            configurationController
         ).create(view, touchEventHandler)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 0289b9a..9d5b17e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -119,6 +119,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -274,6 +275,7 @@
     @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -306,6 +308,7 @@
                 mNotificationListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mock(NotificationEntryManager.class),
                 mock(NotifPipeline.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index a1d9a7b..be1720d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -18,7 +18,9 @@
 
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
 import android.testing.AndroidTestingRunner
+import androidx.core.util.Consumer
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -31,9 +33,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.lang.Exception
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -48,16 +53,28 @@
     @Mock
     private lateinit var deviceStateManager: DeviceStateManager
 
-    private lateinit var foldStateProvider: FoldStateProvider
+    @Mock
+    private lateinit var handler: Handler
+
+    @Captor
+    private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+    @Captor
+    private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
+
+    @Captor
+    private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+
+    private lateinit var foldStateProvider: DeviceFoldStateProvider
 
     private val foldUpdates: MutableList<Int> = arrayListOf()
     private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
 
-    private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
     private var foldedDeviceState: Int = 0
     private var unfoldedDeviceState: Int = 0
 
-    private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+    private var scheduledRunnable: Runnable? = null
+    private var scheduledRunnableDelay: Long? = null
 
     @Before
     fun setUp() {
@@ -75,7 +92,8 @@
             hingeAngleProvider,
             screenStatusProvider,
             deviceStateManager,
-            context.mainExecutor
+            context.mainExecutor,
+            handler
         )
 
         foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
@@ -91,6 +109,22 @@
 
         verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
         verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+        verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
+
+        whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
+            scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
+            scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
+            null
+        }
+
+        whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock ->
+            val removedRunnable = invocationOnMock.getArgument<Runnable>(0)
+            if (removedRunnable == scheduledRunnable) {
+                scheduledRunnableDelay = null
+                scheduledRunnable = null
+            }
+            null
+        }
     }
 
     @Test
@@ -167,6 +201,86 @@
         assertThat(foldUpdates).isEmpty()
     }
 
+    @Test
+    fun startClosingEvent_afterTimeout_abortEmitted() {
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+    }
+
+    @Test
+    fun startClosingEvent_beforeTimeout_abortNotEmitted() {
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
+        sendHingeAngleEvent(180)
+        sendHingeAngleEvent(90)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+        sendHingeAngleEvent(80)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+        sendHingeAngleEvent(180)
+        sendHingeAngleEvent(90)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here.
+        sendHingeAngleEvent(80)
+        simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here.
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+    }
+
+    @Test
+    fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
+        sendHingeAngleEvent(180)
+
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
+        val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
+        for (i in 1..maxAngle) {
+            foldUpdates.clear()
+
+            simulateFolding(startAngle = i)
+
+            assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+            simulateTimeout() // Timeout to set the state to aborted.
+        }
+    }
+
+    private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) {
+        val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
+        if (waitTime >= runnableDelay) {
+            scheduledRunnable?.run()
+            scheduledRunnable = null
+            scheduledRunnableDelay = null
+        }
+    }
+
+    private fun simulateFolding(startAngle: Int) {
+        sendHingeAngleEvent(startAngle)
+        sendHingeAngleEvent(startAngle - 1)
+    }
+
     private fun setFoldState(folded: Boolean) {
         val state = if (folded) foldedDeviceState else unfoldedDeviceState
         foldStateListenerCaptor.value.onStateChanged(state)
@@ -175,4 +289,8 @@
     private fun fireScreenOnEvent() {
         screenOnListenerCaptor.value.onScreenTurnedOn()
     }
+
+    private fun sendHingeAngleEvent(angle: Int) {
+        hingeAngleCaptor.value.accept(angle.toFloat())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index eb54fe0..0f1b65c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -44,6 +44,11 @@
 inline fun <reified T> any(): T = any(T::class.java)
 
 /**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
  * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
  * when null is returned.
  *
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index e3926b4..d3ef6dc 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -24,5 +24,8 @@
         type: "stream",
     },
     srcs: [":services.companion-sources"],
-    libs: ["services.core"],
+    libs: [
+        "app-compat-annotations",
+        "services.core",
+    ],
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
new file mode 100644
index 0000000..a6a8793
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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.companion.virtual;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.window.DisplayWindowPolicyController;
+
+import java.util.List;
+
+
+/**
+ * A controller to control the policies of the windows that can be displayed on the virtual display.
+ */
+class GenericWindowPolicyController extends DisplayWindowPolicyController {
+
+    /**
+     * If required, allow the secure activity to display on remote device since
+     * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+
+    GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+        setInterestedWindowFlags(windowFlags, systemWindowFlags);
+    }
+
+    @Override
+    public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+        // Can't display all the activities if any of them don't want to be displayed.
+        final int activityCount = activities.size();
+        for (int i = 0; i < activityCount; i++) {
+            final ActivityInfo aInfo = activities.get(i);
+            if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
+            int systemWindowFlags) {
+        if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+            return false;
+        }
+        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+                activityInfo.packageName,
+                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
+            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+            if ((windowFlags & FLAG_SECURE) != 0) {
+                return false;
+            }
+            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onTopActivityChanged(ComponentName topActivity, int uid) {
+
+    }
+
+    @Override
+    public void onRunningAppsChanged(int[] runningUids) {
+
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 58d0801..2742608 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.virtual;
 
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -31,6 +34,7 @@
 import android.util.ExceptionUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
@@ -38,11 +42,11 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 
 
-/** @hide */
 @SuppressLint("LongLogTag")
 public class VirtualDeviceManagerService extends SystemService {
 
@@ -81,6 +85,16 @@
     @Override
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
+        publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
+    }
+
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
+        try {
+            return mVirtualDevices.contains(virtualDevice.getAssociationId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @Override
@@ -119,9 +133,15 @@
     private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient {
 
         private final AssociationInfo mAssociationInfo;
+        private final int mOwnerUid;
+        private final GenericWindowPolicyController mGenericWindowPolicyController;
+        private final ArrayList<Integer> mDisplayIds = new ArrayList<>();
 
-        private VirtualDeviceImpl(IBinder token, AssociationInfo associationInfo) {
+        private VirtualDeviceImpl(int ownerUid, IBinder token, AssociationInfo associationInfo) {
+            mOwnerUid = ownerUid;
             mAssociationInfo = associationInfo;
+            mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE,
+                    SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
             try {
                 token.linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -146,6 +166,23 @@
         public void binderDied() {
             close();
         }
+
+        DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
+            if (mDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device already have a virtual display with ID " + displayId);
+            }
+            mDisplayIds.add(displayId);
+            return mGenericWindowPolicyController;
+        }
+
+        void onVirtualDisplayRemovedLocked(int displayId) {
+            if (!mDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device doesn't have a virtual display with ID " + displayId);
+            }
+            mDisplayIds.remove(displayId);
+        }
     }
 
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
@@ -156,10 +193,11 @@
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                     "createVirtualDevice");
-            if (!PermissionUtils.validatePackageName(getContext(), packageName, getCallingUid())) {
+            final int callingUid = getCallingUid();
+            if (!PermissionUtils.validatePackageName(getContext(), packageName, callingUid)) {
                 throw new SecurityException(
                         "Package name " + packageName + " does not belong to calling uid "
-                                + getCallingUid());
+                                + callingUid);
             }
             AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
             if (associationInfo == null) {
@@ -171,7 +209,7 @@
                             "Virtual device for association ID " + associationId
                                     + " already exists");
                 }
-                return new VirtualDeviceImpl(token, associationInfo);
+                return new VirtualDeviceImpl(callingUid, token, associationInfo);
             }
         }
 
@@ -222,4 +260,48 @@
             }
         }
     }
+
+    private final class LocalService extends VirtualDeviceManagerInternal {
+
+        @Override
+        public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
+            synchronized (mVirtualDeviceManagerLock) {
+                return isValidVirtualDeviceLocked(virtualDevice);
+            }
+        }
+
+        @Override
+        public DisplayWindowPolicyController onVirtualDisplayCreated(IVirtualDevice virtualDevice,
+                int displayId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                return ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayCreatedLocked(displayId);
+            }
+        }
+
+        @Override
+        public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
+            }
+        }
+
+        @Override
+        public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    if (mVirtualDevices.valueAt(i).mOwnerUid == uid) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        @Override
+        public boolean isAppRunningOnAnyVirtualDevice(int uid) {
+            // TODO(yukl): Implement this using DWPC.onRunningAppsChanged
+            return false;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 25b36e8..34c21f2 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -88,6 +88,20 @@
     private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5;
 
     /**
+     * Default value of the power button "cooldown" period after the Emergency gesture is triggered.
+     * See {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
+     */
+    private static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT = 3000;
+
+    /**
+     * Maximum value of the power button "cooldown" period after the Emergency gesture is triggered.
+     * The value read from {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
+     * is capped at this maximum.
+     */
+    @VisibleForTesting
+    static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
+
+    /**
      * Number of taps required to launch camera shortcut.
      */
     private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
@@ -163,7 +177,14 @@
      */
     private boolean mEmergencyGestureEnabled;
 
+    /**
+     * Power button cooldown period in milliseconds, after emergency gesture is triggered. A zero
+     * value means the cooldown period is disabled.
+     */
+    private int mEmergencyGesturePowerButtonCooldownPeriodMs;
+
     private long mLastPowerDown;
+    private long mLastEmergencyGestureTriggered;
     private int mPowerButtonConsecutiveTaps;
     private int mPowerButtonSlowConsecutiveTaps;
     private final UiEventLogger mUiEventLogger;
@@ -231,6 +252,7 @@
             updateCameraRegistered();
             updateCameraDoubleTapPowerEnabled();
             updateEmergencyGestureEnabled();
+            updateEmergencyGesturePowerButtonCooldownPeriodMs();
 
             mUserId = ActivityManager.getCurrentUser();
             mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
@@ -261,6 +283,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
                 false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(
+                        Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS),
+                false, mSettingObserver, mUserId);
     }
 
     private void updateCameraRegistered() {
@@ -294,6 +320,14 @@
         }
     }
 
+    @VisibleForTesting
+    void updateEmergencyGesturePowerButtonCooldownPeriodMs() {
+        int cooldownPeriodMs = getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, mUserId);
+        synchronized (this) {
+            mEmergencyGesturePowerButtonCooldownPeriodMs = cooldownPeriodMs;
+        }
+    }
+
     private void unregisterCameraLaunchGesture() {
         if (mCameraLaunchRegistered) {
             mCameraLaunchRegistered = false;
@@ -428,6 +462,20 @@
     }
 
     /**
+     * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
+     * value is capped at a maximum
+     * {@link GestureLauncherService#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX}. If the
+     * value is zero, it means the cooldown period is disabled.
+     */
+    @VisibleForTesting
+    static int getEmergencyGesturePowerButtonCooldownPeriodMs(Context context, int userId) {
+        int cooldown = Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT, userId);
+        return Math.min(cooldown, EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX);
+    }
+
+    /**
      * Whether to enable the camera launch gesture.
      */
     private static boolean isCameraLaunchEnabled(Resources resources) {
@@ -475,10 +523,24 @@
      */
     public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
             MutableBoolean outLaunched) {
+        if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
+                && event.getEventTime() - mLastEmergencyGestureTriggered
+                < mEmergencyGesturePowerButtonCooldownPeriodMs) {
+            Slog.i(TAG, String.format(
+                    "Suppressing power button: within %dms cooldown period after Emergency "
+                            + "Gesture. Begin=%dms, end=%dms.",
+                    mEmergencyGesturePowerButtonCooldownPeriodMs,
+                    mLastEmergencyGestureTriggered,
+                    mLastEmergencyGestureTriggered + mEmergencyGesturePowerButtonCooldownPeriodMs));
+            outLaunched.value = false;
+            return true;
+        }
+
         if (event.isLongPress()) {
             // Long presses are sent as a second key down. If the long press threshold is set lower
             // than the double tap of sequence interval thresholds, this could cause false double
             // taps or consecutive taps, so we want to ignore the long press event.
+            outLaunched.value = false;
             return false;
         }
         boolean launchCamera = false;
@@ -542,6 +604,12 @@
             Slog.i(TAG, "Emergency gesture detected, launching.");
             launchEmergencyGesture = handleEmergencyGesture();
             mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+            // Record emergency trigger time if emergency UI was launched
+            if (launchEmergencyGesture) {
+                synchronized (this) {
+                    mLastEmergencyGestureTriggered = event.getEventTime();
+                }
+            }
         }
         mMetricsLogger.histogram("power_consecutive_short_tap_count",
                 mPowerButtonSlowConsecutiveTaps);
@@ -670,6 +738,7 @@
                 updateCameraRegistered();
                 updateCameraDoubleTapPowerEnabled();
                 updateEmergencyGestureEnabled();
+                updateEmergencyGesturePowerButtonCooldownPeriodMs();
             }
         }
     };
@@ -680,6 +749,7 @@
                 updateCameraRegistered();
                 updateCameraDoubleTapPowerEnabled();
                 updateEmergencyGestureEnabled();
+                updateEmergencyGesturePowerButtonCooldownPeriodMs();
             }
         }
     };
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b7b4870..9180ef8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -45,7 +45,6 @@
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 
 import libcore.util.EmptyArray;
@@ -260,43 +259,6 @@
     }
 
     @Override
-    public Future<?> scheduleReadProcStateCpuTimes(
-            boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
-        synchronized (mStats) {
-            if (!mStats.trackPerProcStateCpuTimes()) {
-                return null;
-            }
-        }
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (!mExecutorService.isShutdown()) {
-                return mExecutorService.schedule(PooledLambda.obtainRunnable(
-                        BatteryStatsImpl::updateProcStateCpuTimes,
-                        mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
-                        delayMillis, TimeUnit.MILLISECONDS);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Future<?> scheduleCopyFromAllUidsCpuTimes(
-            boolean onBattery, boolean onBatteryScreenOff) {
-        synchronized (mStats) {
-            if (!mStats.trackPerProcStateCpuTimes()) {
-                return null;
-            }
-        }
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (!mExecutorService.isShutdown()) {
-                return mExecutorService.submit(PooledLambda.obtainRunnable(
-                        BatteryStatsImpl::copyFromAllUidsCpuTimes,
-                        mStats, onBattery, onBatteryScreenOff).recycleOnUse());
-            }
-        }
-        return null;
-    }
-
-    @Override
     public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
             boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
         synchronized (BatteryExternalStatsWorker.this) {
@@ -491,7 +453,7 @@
                 }
 
                 if ((updateFlags & UPDATE_CPU) != 0) {
-                    mStats.copyFromAllUidsCpuTimes();
+                    mStats.updateCpuTimesForAllUids();
                 }
 
                 // Clean up any UIDs if necessary.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e79cba1..a7864b9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -145,9 +145,11 @@
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
 
-    // Amount of time we wait for observers to handle a user switch before we log a warning.
-    // Must be smaller than USER_SWITCH_TIMEOUT_MS.
-    private static final int USER_SWITCH_WARNING_TIMEOUT_MS = 500;
+    /**
+     * Amount of time we wait for an observer to handle a user switch before we log a warning. This
+     * wait time is per observer.
+     */
+    private static final int LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS = 500;
 
     // ActivityManager thread message constants
     static final int REPORT_USER_SWITCH_MSG = 10;
@@ -1916,6 +1918,7 @@
             final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
             final long dispatchStartedTime = SystemClock.elapsedRealtime();
             for (int i = 0; i < observerCount; i++) {
+                final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
                 try {
                     // Prepend with unique prefix to guarantee that keys are unique
                     final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
@@ -1926,13 +1929,20 @@
                         @Override
                         public void sendResult(Bundle data) throws RemoteException {
                             synchronized (mLock) {
-                                long delay = SystemClock.elapsedRealtime() - dispatchStartedTime;
-                                if (delay > USER_SWITCH_TIMEOUT_MS) {
-                                    Slogf.e(TAG, "User switch timeout: observer " + name
-                                            + " sent result after " + delay + " ms");
-                                } else if (delay > USER_SWITCH_WARNING_TIMEOUT_MS) {
+                                long delayForObserver = SystemClock.elapsedRealtime()
+                                        - dispatchStartedTimeForObserver;
+                                if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
                                     Slogf.w(TAG, "User switch slowed down by observer " + name
-                                            + ": result sent after " + delay + " ms");
+                                            + ": result took " + delayForObserver
+                                            + " ms to process.");
+                                }
+
+                                long totalDelay = SystemClock.elapsedRealtime()
+                                        - dispatchStartedTime;
+                                if (totalDelay > USER_SWITCH_TIMEOUT_MS) {
+                                    Slogf.e(TAG, "User switch timeout: observer " + name
+                                            + "'s result was received " + totalDelay
+                                            + " ms after dispatchUserSwitch.");
                                 }
 
                                 curWaitingUserSwitchCallbacks.remove(name);
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
index 4eac972..f05f403 100644
--- a/services/core/java/com/android/server/biometrics/OWNERS
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -6,3 +6,4 @@
 ilyamaty@google.com
 joshmccloskey@google.com
 jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 7341e74..358263d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -503,10 +503,14 @@
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
-        } else if (isSettings()) {
-            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else if (isBiometricPrompt()) {
+            // BP reason always takes precedent over settings, since callers from within
+            // settings can always invoke BP.
             return BiometricOverlayConstants.REASON_AUTH_BP;
+        } else if (isSettings()) {
+            // This is pretty much only for FingerprintManager#authenticate usage from
+            // FingerprintSettings.
+            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else {
             return BiometricOverlayConstants.REASON_AUTH_OTHER;
         }
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
index 8d9b13e..3bf6ca2 100644
--- a/services/core/java/com/android/server/communal/CommunalManagerService.java
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -30,6 +30,7 @@
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.communal.ICommunalManager;
+import android.app.communal.ICommunalModeListener;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -42,6 +43,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
@@ -77,6 +80,8 @@
     private final PackageReceiver mPackageReceiver;
     private final PackageManager mPackageManager;
     private final DreamManagerInternal mDreamManagerInternal;
+    private final RemoteCallbackList<ICommunalModeListener> mListeners =
+            new RemoteCallbackList<>();
 
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
@@ -129,7 +134,7 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.COMMUNAL_MANAGER_SERVICE, mBinderService);
+        publishBinderService(Context.COMMUNAL_SERVICE, mBinderService);
     }
 
     @Override
@@ -242,6 +247,27 @@
         return !isAppAllowed(appInfo);
     }
 
+    private void dispatchCommunalMode(boolean isShowing) {
+        synchronized (mListeners) {
+            int i = mListeners.beginBroadcast();
+            while (i > 0) {
+                i--;
+                try {
+                    mListeners.getBroadcastItem(i).onCommunalModeChanged(isShowing);
+                } catch (RemoteException e) {
+                    // Handled by the RemoteCallbackList.
+                }
+            }
+            mListeners.finishBroadcast();
+        }
+    }
+
+    private void enforceReadPermission() {
+        mContext.enforceCallingPermission(Manifest.permission.READ_COMMUNAL_STATE,
+                Manifest.permission.READ_COMMUNAL_STATE
+                        + "permission required to read communal state.");
+    }
+
     private final class BinderService extends ICommunalManager.Stub {
         /**
          * Sets whether or not we are in communal mode.
@@ -252,7 +278,43 @@
             mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE,
                     Manifest.permission.WRITE_COMMUNAL_STATE
                             + "permission required to modify communal state.");
+            if (mCommunalViewIsShowing.get() == isShowing) {
+                return;
+            }
             mCommunalViewIsShowing.set(isShowing);
+            dispatchCommunalMode(isShowing);
+        }
+
+        /**
+         * Checks whether or not we are in communal mode.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        @Override
+        public boolean isCommunalMode() {
+            enforceReadPermission();
+            return mCommunalViewIsShowing.get();
+        }
+
+        /**
+         * Adds a callback to execute when communal state changes.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        public void addCommunalModeListener(ICommunalModeListener listener) {
+            enforceReadPermission();
+            synchronized (mListeners) {
+                mListeners.register(listener);
+            }
+        }
+
+        /**
+         * Removes an added callback that execute when communal state changes.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        public void removeCommunalModeListener(ICommunalModeListener listener) {
+            enforceReadPermission();
+            synchronized (mListeners) {
+                mListeners.unregister(listener);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
new file mode 100644
index 0000000..39fa3f2
--- /dev/null
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.companion.virtual;
+
+import android.companion.virtual.IVirtualDevice;
+import android.window.DisplayWindowPolicyController;
+
+/**
+ * Virtual device manager local service interface.
+ * Only for use within system server.
+ */
+public abstract class VirtualDeviceManagerInternal {
+
+    /**
+     * Validate the virtual device.
+     */
+    public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
+
+    /**
+     * Notify a virtual display is created.
+     *
+     * @param virtualDevice The virtual device where the virtual display located.
+     * @param displayId The display id of the created virtual display.
+     *
+     * @return The {@link DisplayWindowPolicyController} of the virtual device.
+     */
+    public abstract DisplayWindowPolicyController onVirtualDisplayCreated(
+            IVirtualDevice virtualDevice, int displayId);
+
+    /**
+     * Notify a virtual display is removed.
+     *
+     * @param virtualDevice The virtual device where the virtual display located.
+     * @param displayId The display id of the removed virtual display.
+     */
+    public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
+
+    /**
+     * Returns true if the given {@code uid} is the owner of any virtual devices that are
+     * currently active.
+     */
+    public abstract boolean isAppOwnerOfAnyVirtualDevice(int uid);
+
+    /**
+     * Returns true if the given {@code uid} is currently running on any virtual devices. This is
+     * determined by whether the app has any activities in the task stack on a virtual-device-owned
+     * display.
+     */
+    public abstract boolean isAppRunningOnAnyVirtualDevice(int uid);
+}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index e9af6011..c04032f 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -87,7 +87,10 @@
     private final Sensor mLightSensor;
 
     // The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
-    private final BrightnessMappingStrategy mBrightnessMapper;
+    @Nullable
+    private BrightnessMappingStrategy mCurrentBrightnessMapper;
+    private final BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+    private final BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
     // The minimum and maximum screen brightnesses.
     private final float mScreenBrightnessRangeMinimum;
@@ -215,36 +218,41 @@
     private final Injector mInjector;
 
     AutomaticBrightnessController(Callbacks callbacks, Looper looper,
-            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+            SensorManager sensorManager, Sensor lightSensor,
+            BrightnessMappingStrategy interactiveModeBrightnessMapper,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController) {
-        this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
+            HighBrightnessModeController hbmController,
+            BrightnessMappingStrategy idleModeBrightnessMapper) {
+        this(new Injector(), callbacks, looper, sensorManager, lightSensor,
+                interactiveModeBrightnessMapper,
                 lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
                 ambientBrightnessThresholds, screenBrightnessThresholds, context,
-                hbmController
+                hbmController, idleModeBrightnessMapper
         );
     }
 
     @VisibleForTesting
     AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
-            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+            SensorManager sensorManager, Sensor lightSensor,
+            BrightnessMappingStrategy interactiveModeBrightnessMapper,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController) {
+            HighBrightnessModeController hbmController,
+            BrightnessMappingStrategy idleModeBrightnessMapper) {
         mInjector = injector;
         mContext = context;
         mCallbacks = callbacks;
         mSensorManager = sensorManager;
-        mBrightnessMapper = mapper;
+        mCurrentBrightnessMapper = interactiveModeBrightnessMapper;
         mScreenBrightnessRangeMinimum = brightnessMin;
         mScreenBrightnessRangeMaximum = brightnessMax;
         mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
@@ -277,6 +285,10 @@
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mHbmController = hbmController;
+        mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
+        mIdleModeBrightnessMapper = idleModeBrightnessMapper;
+        // Initialize to active (normal) screen brightness mode
+        switchToInteractiveScreenBrightnessMode();
     }
 
     /**
@@ -291,7 +303,12 @@
         if (mLoggingEnabled == loggingEnabled) {
             return false;
         }
-        mBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        if (mInteractiveModeBrightnessMapper != null) {
+            mInteractiveModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        }
+        if (mIdleModeBrightnessMapper != null) {
+            mIdleModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        }
         mLoggingEnabled = loggingEnabled;
         return true;
     }
@@ -311,7 +328,7 @@
     }
 
     public float getAutomaticScreenBrightnessAdjustment() {
-        return mBrightnessMapper.getAutoBrightnessAdjustment();
+        return mCurrentBrightnessMapper.getAutoBrightnessAdjustment();
     }
 
     public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
@@ -350,15 +367,20 @@
     }
 
     public boolean hasUserDataPoints() {
-        return mBrightnessMapper.hasUserDataPoints();
+        return mCurrentBrightnessMapper.hasUserDataPoints();
     }
 
+    // Used internally to establish whether we have deviated from the default config.
     public boolean isDefaultConfig() {
-        return mBrightnessMapper.isDefaultConfig();
+        if (isInIdleMode()) {
+            return false;
+        }
+        return mInteractiveModeBrightnessMapper.isDefaultConfig();
     }
 
+    // Called from APIs to get the configuration.
     public BrightnessConfiguration getDefaultConfig() {
-        return mBrightnessMapper.getDefaultConfig();
+        return mInteractiveModeBrightnessMapper.getDefaultConfig();
     }
 
     /**
@@ -379,7 +401,7 @@
         }
         if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
             mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
-                    mBrightnessMapper.getShortTermModelTimeout());
+                    mCurrentBrightnessMapper.getShortTermModelTimeout());
         } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
             mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
         }
@@ -398,7 +420,7 @@
             // and we can't use this data to add a new control point to the short-term model.
             return false;
         }
-        mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
+        mCurrentBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
         mShortTermModelValid = true;
         mShortTermModelAnchor = mAmbientLux;
         if (mLoggingEnabled) {
@@ -408,7 +430,7 @@
     }
 
     public void resetShortTermModel() {
-        mBrightnessMapper.clearUserDataPoints();
+        mCurrentBrightnessMapper.clearUserDataPoints();
         mShortTermModelValid = true;
         mShortTermModelAnchor = -1;
     }
@@ -421,13 +443,19 @@
     }
 
     public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
-        if (mBrightnessMapper.setBrightnessConfiguration(configuration)) {
-            resetShortTermModel();
+        if (mInteractiveModeBrightnessMapper.setBrightnessConfiguration(configuration)) {
+            if (!isInIdleMode()) {
+                resetShortTermModel();
+            }
             return true;
         }
         return false;
     }
 
+    public boolean isInIdleMode() {
+        return mCurrentBrightnessMapper.isForIdleMode();
+    }
+
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
@@ -461,7 +489,12 @@
         pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
         pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
         pw.println("  mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
-        pw.println("  mShortTermModelTimeout=" + mBrightnessMapper.getShortTermModelTimeout());
+        pw.println("  mShortTermModelTimeout(active)="
+                + mInteractiveModeBrightnessMapper.getShortTermModelTimeout());
+        if (mIdleModeBrightnessMapper != null) {
+            pw.println("  mShortTermModelTimeout(idle)="
+                    + mIdleModeBrightnessMapper.getShortTermModelTimeout());
+        }
         pw.println("  mShortTermModelAnchor=" + mShortTermModelAnchor);
         pw.println("  mShortTermModelValid=" + mShortTermModelValid);
         pw.println("  mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
@@ -472,9 +505,15 @@
         pw.println("  mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
         pw.println("  mForegroundAppCategory=" + mForegroundAppCategory);
         pw.println("  mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
+        pw.println("  Idle mode active=" + mCurrentBrightnessMapper.isForIdleMode());
 
         pw.println();
-        mBrightnessMapper.dump(pw);
+        pw.println("  mActiveMapper=");
+        mInteractiveModeBrightnessMapper.dump(pw);
+        if (mIdleModeBrightnessMapper != null) {
+            pw.println("  mIdleMapper=");
+            mIdleModeBrightnessMapper.dump(pw);
+        }
 
         pw.println();
         mAmbientBrightnessThresholds.dump(pw);
@@ -544,7 +583,7 @@
     }
 
     private boolean setAutoBrightnessAdjustment(float adjustment) {
-        return mBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
+        return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
     }
 
     private void setAmbientLux(float lux) {
@@ -562,7 +601,8 @@
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
         if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
-            if (mBrightnessMapper.shouldResetShortTermModel(mAmbientLux, mShortTermModelAnchor)) {
+            if (mCurrentBrightnessMapper.shouldResetShortTermModel(
+                    mAmbientLux, mShortTermModelAnchor)) {
                 resetShortTermModel();
             } else {
                 mShortTermModelValid = true;
@@ -743,7 +783,7 @@
             return;
         }
 
-        float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
+        float value = mCurrentBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
                 mForegroundAppCategory);
         float newScreenAutoBrightness = clampScreenBrightness(value);
 
@@ -909,6 +949,41 @@
         updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
     }
 
+    void switchToIdleMode() {
+        if (mIdleModeBrightnessMapper == null) {
+            return;
+        }
+        if (mCurrentBrightnessMapper.isForIdleMode()) {
+            return;
+        }
+        Slog.i(TAG, "Switching to Idle Screen Brightness Mode");
+        mCurrentBrightnessMapper = mIdleModeBrightnessMapper;
+        resetShortTermModel();
+        update();
+    }
+
+    void switchToInteractiveScreenBrightnessMode() {
+        if (!mCurrentBrightnessMapper.isForIdleMode()) {
+            return;
+        }
+        Slog.i(TAG, "Switching to Interactive Screen Brightness Mode");
+        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
+        resetShortTermModel();
+        update();
+    }
+
+    public float convertToNits(float brightness) {
+        if (mCurrentBrightnessMapper != null) {
+            return mCurrentBrightnessMapper.convertToNits(brightness);
+        } else {
+            return -1.0f;
+        }
+    }
+
+    public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
+        mCurrentBrightnessMapper.recalculateSplines(applyAdjustment, adjustment);
+    }
+
     private final class AutomaticBrightnessHandler extends Handler {
         public AutomaticBrightnessHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index beb4d5b..240168b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -135,7 +135,7 @@
             builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
-                    autoBrightnessAdjustmentMaxGamma);
+                    autoBrightnessAdjustmentMaxGamma, isForIdleMode);
         } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
             return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
@@ -355,6 +355,12 @@
     public abstract void dump(PrintWriter pw);
 
     /**
+     * We can designate a mapping strategy to be used for idle screen brightness mode.
+     * @return whether this mapping strategy is to be used for idle screen brightness mode.
+     */
+    public abstract boolean isForIdleMode();
+
+    /**
      * Check if the short term model should be reset given the anchor lux the last
      * brightness change was made at and the current ambient lux.
      */
@@ -711,6 +717,11 @@
             pw.println("  mUserBrightness=" + mUserBrightness);
         }
 
+        @Override
+        public boolean isForIdleMode() {
+            return false;
+        }
+
         private void computeSpline() {
             Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
@@ -758,9 +769,10 @@
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
         private float mUserBrightness;
+        private final boolean mIsForIdleMode;
 
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
-                float[] brightness, float maxGamma) {
+                float[] brightness, float maxGamma, boolean isForIdleMode) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
                     "Nits and brightness arrays must not be empty!");
@@ -772,6 +784,7 @@
             Preconditions.checkArrayElementsInRange(brightness,
                     PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness");
 
+            mIsForIdleMode = isForIdleMode;
             mMaxGamma = maxGamma;
             mAutoBrightnessAdjustment = 0;
             mUserLux = -1;
@@ -933,6 +946,11 @@
             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
         }
 
+        @Override
+        public boolean isForIdleMode() {
+            return mIsForIdleMode;
+        }
+
         private void computeNitsBrightnessSplines(float[] nits) {
             mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness);
             mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 77ab813..3f55848 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -42,6 +42,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.compat.CompatChanges;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
@@ -126,6 +127,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.wm.SurfaceAnimationThread;
@@ -244,9 +246,12 @@
     public final SparseArray<CallbackRecord> mCallbacks =
             new SparseArray<CallbackRecord>();
 
-    /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */
-    final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController =
-            new SparseArray<>();
+    /**
+     *  All {@link IVirtualDevice} and {@link DisplayWindowPolicyController}s indexed by
+     *  {@link DisplayInfo#displayId}.
+     */
+    final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>>
+            mDisplayWindowPolicyControllers = new SparseArray<>();
 
     // List of all currently registered display adapters.
     private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
@@ -1180,8 +1185,8 @@
     }
 
     private int createVirtualDisplayInternal(VirtualDisplayConfig virtualDisplayConfig,
-            IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-            DisplayWindowPolicyController controller) {
+            IVirtualDisplayCallback callback, IMediaProjection projection,
+            IVirtualDevice virtualDevice, String packageName) {
         final int callingUid = Binder.getCallingUid();
         if (!validatePackageName(callingUid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
@@ -1226,6 +1231,14 @@
             }
         }
 
+        if (virtualDevice != null) {
+            final VirtualDeviceManagerInternal vdm =
+                    getLocalService(VirtualDeviceManagerInternal.class);
+            if (!vdm.isValidVirtualDevice(virtualDevice)) {
+                throw new SecurityException("Invalid virtual device");
+            }
+        }
+
         if (callingUid != Process.SYSTEM_UID
                 && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
             if (!canProjectVideo(projection)) {
@@ -1306,8 +1319,8 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
-                return createVirtualDisplayLocked(callback, projection, callingUid, packageName,
-                        surface, flags, virtualDisplayConfig, controller);
+                return createVirtualDisplayLocked(callback, projection, virtualDevice, callingUid,
+                        packageName, surface, flags, virtualDisplayConfig);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -1315,9 +1328,9 @@
     }
 
     private int createVirtualDisplayLocked(IVirtualDisplayCallback callback,
-            IMediaProjection projection, int callingUid, String packageName, Surface surface,
-            int flags, VirtualDisplayConfig virtualDisplayConfig,
-            DisplayWindowPolicyController controller) {
+            IMediaProjection projection, IVirtualDevice virtualDevice,
+            int callingUid, String packageName, Surface surface,
+            int flags, VirtualDisplayConfig virtualDisplayConfig) {
         if (mVirtualDisplayAdapter == null) {
             Slog.w(TAG, "Rejecting request to create private virtual display "
                     + "because the virtual display adapter is not available.");
@@ -1344,10 +1357,16 @@
 
         final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
         if (display != null) {
-            if (controller != null) {
-                mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller);
+            final int displayId = display.getDisplayIdLocked();
+            if (virtualDevice != null) {
+                final VirtualDeviceManagerInternal vdm =
+                        getLocalService(VirtualDeviceManagerInternal.class);
+                final DisplayWindowPolicyController controller =
+                        vdm.onVirtualDisplayCreated(virtualDevice, displayId);
+                mDisplayWindowPolicyControllers.put(displayId,
+                        Pair.create(virtualDevice, controller));
             }
-            return display.getDisplayIdLocked();
+            return displayId;
         }
 
         // Something weird happened and the logical display was not created.
@@ -1391,7 +1410,13 @@
             if (device != null) {
                 final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
                 if (display != null) {
-                    mDisplayWindowPolicyController.delete(display.getDisplayIdLocked());
+                    final int displayId = display.getDisplayIdLocked();
+                    if (mDisplayWindowPolicyControllers.contains(displayId)) {
+                        Pair<IVirtualDevice, DisplayWindowPolicyController> pair =
+                                mDisplayWindowPolicyControllers.removeReturnOld(displayId);
+                        getLocalService(VirtualDeviceManagerInternal.class)
+                                .onVirtualDisplayRemoved(pair.first, displayId);
+                    }
                 }
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
@@ -2345,13 +2370,13 @@
             pw.println();
             mPersistentDataStore.dump(pw);
 
-            final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size();
+            final int displayWindowPolicyControllerCount = mDisplayWindowPolicyControllers.size();
             pw.println();
             pw.println("Display Window Policy Controllers: size="
                     + displayWindowPolicyControllerCount);
             for (int i = 0; i < displayWindowPolicyControllerCount; i++) {
-                pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":");
-                mDisplayWindowPolicyController.valueAt(i).dump("  ", pw);
+                pw.print("Display " + mDisplayWindowPolicyControllers.keyAt(i) + ":");
+                mDisplayWindowPolicyControllers.valueAt(i).second.dump("  ", pw);
             }
         }
         pw.println();
@@ -2922,9 +2947,10 @@
 
         @Override // Binder call
         public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
+                IVirtualDisplayCallback callback, IMediaProjection projection,
+                IVirtualDevice virtualDeviceToken, String packageName) {
             return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection,
-                    packageName, null /* controller */);
+                    virtualDeviceToken, packageName);
         }
 
         @Override // Binder call
@@ -3690,17 +3716,12 @@
         }
 
         @Override
-        public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-                DisplayWindowPolicyController controller) {
-            return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection,
-                    packageName, controller);
-        }
-
-        @Override
         public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) {
             synchronized (mSyncRoot) {
-                return mDisplayWindowPolicyController.get(displayId);
+                if (mDisplayWindowPolicyControllers.contains(displayId)) {
+                    return mDisplayWindowPolicyControllers.get(displayId).second;
+                }
+                return null;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7f78cac..b46ee27 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -386,12 +386,8 @@
 
     private Sensor mLightSensor;
 
-    // The mapper between ambient lux, display backlight values, and display brightness.
-    // This mapper holds the current one that is being used. We will switch between the idle
-    // mapper and active mapper here.
-    @Nullable
-    private BrightnessMappingStrategy mCurrentBrightnessMapper;
-
+    // The mappers between ambient lux, display backlight values, and display brightness.
+    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
     // Mapper used for active (normal) screen brightness mode
     @Nullable
     private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
@@ -399,6 +395,11 @@
     @Nullable
     private BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
+    // If these are both true, and mIdleModeBrightnessMapper != null,
+    // then we are in idle screen brightness mode.
+    private boolean mIsDreaming;
+    private boolean mIsDocked;
+
     // The current brightness configuration.
     @Nullable
     private BrightnessConfiguration mBrightnessConfiguration;
@@ -610,8 +611,14 @@
     }
 
     private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
-        if (mCurrentBrightnessMapper == null) {
-            Log.w(TAG, "No brightness mapping available to recalculate splines");
+        if (mAutomaticBrightnessController == null) {
+            return;
+        }
+        if ((!mAutomaticBrightnessController.isInIdleMode()
+                && mInteractiveModeBrightnessMapper == null)
+                || (mAutomaticBrightnessController.isInIdleMode()
+                && mIdleModeBrightnessMapper == null)) {
+            Log.w(TAG, "No brightness mapping available to recalculate splines for this mode");
             return;
         }
 
@@ -619,7 +626,7 @@
         for (int i = 0; i < mNitsRange.length; i++) {
             adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
         }
-        mCurrentBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(),
+        mAutomaticBrightnessController.recalculateSplines(mCdsi.isReduceBrightColorsActivated(),
                 adjustedNits);
 
         mPendingRbcOnOrChanged = strengthChanged || justActivated;
@@ -886,9 +893,8 @@
             mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
                     mDisplayDeviceConfig);
         }
-        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
 
-        if (mCurrentBrightnessMapper != null) {
+        if (mInteractiveModeBrightnessMapper != null) {
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
@@ -939,12 +945,13 @@
                 mAutomaticBrightnessController.stop();
             }
             mAutomaticBrightnessController = new AutomaticBrightnessController(this,
-                    handler.getLooper(), mSensorManager, mLightSensor, mCurrentBrightnessMapper,
-                    lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
-                    initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
-                    autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
-                    screenBrightnessThresholds, mContext, mHbmController);
+                    handler.getLooper(), mSensorManager, mLightSensor,
+                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
+                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
+                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
+                    darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
+                    ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
+                    mHbmController, mIdleModeBrightnessMapper);
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
         }
@@ -974,6 +981,16 @@
         }
     }
 
+    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+        if (mAutomaticBrightnessController != null) {
+            if (isIdle) {
+                mAutomaticBrightnessController.switchToIdleMode();
+            } else {
+                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
+            }
+        }
+    }
+
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
         @Override
         public void onAnimationStart(Animator animation) {
@@ -1422,9 +1439,14 @@
                 }
             }
 
-            if (!brightnessIsTemporary) {
+            // Report brightness to brightnesstracker:
+            // If brightness is not temporary (ie the slider has been released)
+            // AND if we are not in idle screen brightness mode.
+            if (!brightnessIsTemporary
+                    && (mAutomaticBrightnessController != null
+                            && !mAutomaticBrightnessController.isInIdleMode())) {
                 if (userInitiatedChange && (mAutomaticBrightnessController == null
-                        || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+                            || !mAutomaticBrightnessController.hasValidAmbientLux())) {
                     // If we don't have a valid lux reading we can't report a valid
                     // slider event so notify as if the system changed the brightness.
                     userInitiatedChange = false;
@@ -2162,11 +2184,10 @@
     }
 
     private float convertToNits(float brightness) {
-        if (mCurrentBrightnessMapper != null) {
-            return mCurrentBrightnessMapper.convertToNits(brightness);
-        } else {
-            return -1.0f;
+        if (mAutomaticBrightnessController == null) {
+            return -1f;
         }
+        return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
     private void updatePendingProximityRequestsLocked() {
@@ -2315,11 +2336,6 @@
         pw.println("  mReportedToPolicy=" +
                 reportedToPolicyToString(mReportedScreenStateToPolicy));
 
-        if (mIdleModeBrightnessMapper != null) {
-            pw.println("  mIdleModeBrightnessMapper= ");
-            mIdleModeBrightnessMapper.dump(pw);
-        }
-
         if (mScreenBrightnessRampAnimator != null) {
             pw.println("  mScreenBrightnessRampAnimator.isAnimating()=" +
                     mScreenBrightnessRampAnimator.isAnimating());
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
new file mode 100644
index 0000000..b2bd47b
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2021 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.locales;
+
+import static android.os.UserHandle.USER_NULL;
+
+import static com.android.server.locales.LocaleManagerService.DEBUG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.backup.BackupManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.BestClock;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.HandlerThread;
+import android.os.LocaleList;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class for managing backup and restore of app-specific locales.
+ */
+class LocaleManagerBackupHelper {
+    private static final String TAG = "LocaleManagerBkpHelper"; // must be < 23 chars
+
+    // Tags and attributes for xml.
+    private static final String LOCALES_XML_TAG = "locales";
+    private static final String PACKAGE_XML_TAG = "package";
+    private static final String ATTR_PACKAGE_NAME = "name";
+    private static final String ATTR_LOCALES = "locales";
+    private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis";
+
+    private static final String STAGE_FILE_NAME = "staged_locales";
+    private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
+
+    private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile(
+            TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME));
+    private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2;
+    private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3);
+
+    private final LocaleManagerService mLocaleManagerService;
+    private final PackageManagerInternal mPackageManagerInternal;
+    private final File mStagedLocalesDir;
+    private final Clock mClock;
+    private final Context mContext;
+    private final Object mStagedDataLock = new Object();
+
+    // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using
+    // SparseArray because it is more memory-efficient than a HashMap.
+    private final SparseArray<StagedData> mStagedData = new SparseArray<>();
+
+    private final PackageMonitor mPackageMonitor;
+    private final BroadcastReceiver mUserMonitor;
+
+    LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal) {
+        this(localeManagerService.mContext, localeManagerService, pmInternal,
+                new File(Environment.getDataSystemCeDirectory(),
+                        "app_locales"), getDefaultClock());
+    }
+
+    private static @NonNull Clock getDefaultClock() {
+        return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
+                Clock.systemUTC());
+    }
+
+    @VisibleForTesting LocaleManagerBackupHelper(Context context,
+            LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
+        mContext = context;
+        mLocaleManagerService = localeManagerService;
+        mPackageManagerInternal = pmInternal;
+        mClock = clock;
+        mStagedLocalesDir = stagedLocalesDir;
+
+        loadAllStageFiles();
+
+        HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND);
+        broadcastHandlerThread.start();
+
+        mPackageMonitor = new PackageMonitorImpl();
+        mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
+                UserHandle.ALL,
+                true);
+        mUserMonitor = new UserMonitor();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        context.registerReceiverAsUser(mUserMonitor, UserHandle.ALL, filter,
+                null, broadcastHandlerThread.getThreadHandler());
+    }
+
+    @VisibleForTesting
+    BroadcastReceiver getUserMonitor() {
+        return mUserMonitor;
+    }
+
+    @VisibleForTesting
+    PackageMonitor getPackageMonitor() {
+        return mPackageMonitor;
+    }
+
+    /**
+     * Loads the staged data into memory by reading all the files in the staged directory.
+     *
+     * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the
+     * constructor (before any broadcast receivers are registered).
+     */
+    private void loadAllStageFiles() {
+        File[] files = mStagedLocalesDir.listFiles();
+        if (files == null) {
+            return;
+        }
+        for (File file : files) {
+            String fileName = file.getName();
+            Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName);
+            if (!matcher.matches()) {
+                file.delete();
+                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                        "Unrecognized file"));
+                continue;
+            }
+            try {
+                final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN));
+                StagedData stagedData = readStageFile(file);
+                if (stagedData != null) {
+                    mStagedData.put(userId, stagedData);
+                } else {
+                    file.delete();
+                    Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                            "Could not read file"));
+                }
+            } catch (NumberFormatException e) {
+                file.delete();
+                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                        "Could not parse user id from file name"));
+            }
+        }
+    }
+
+    /**
+     * Loads the stage file from the disk and parses it into a list of app backups.
+     */
+    private @Nullable StagedData readStageFile(@NonNull File file) {
+        InputStream stagedDataInputStream = null;
+        AtomicFile stageFile = new AtomicFile(file);
+        try {
+            stagedDataInputStream = stageFile.openRead();
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name());
+
+            XmlUtils.beginDocument(parser, LOCALES_XML_TAG);
+            long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null,
+                    ATTR_CREATION_TIME_MILLIS);
+            return new StagedData(creationTimeMillis, readFromXml(parser));
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(TAG, "Could not parse stage file ", e);
+        } finally {
+            IoUtils.closeQuietly(stagedDataInputStream);
+        }
+        return null;
+    }
+
+    /**
+     * @see LocaleManagerInternal#getBackupPayload(int userId)
+     */
+    public byte[] getBackupPayload(int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "getBackupPayload invoked for user id " + userId);
+        }
+
+        synchronized (mStagedDataLock) {
+            cleanStagedDataForOldEntriesLocked();
+        }
+
+        HashMap<String, String> pkgStates = new HashMap<>();
+        for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
+                userId, Binder.getCallingUid())) {
+            try {
+                LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
+                        appInfo.packageName,
+                        userId);
+                // Backup locales only for apps which do have app-specific overrides.
+                if (!appLocales.isEmpty()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Add package=" + appInfo.packageName + " locales="
+                                + appLocales.toLanguageTags() + " to backup payload");
+                    }
+                    pkgStates.put(appInfo.packageName, appLocales.toLanguageTags());
+                }
+            } catch (RemoteException | IllegalArgumentException e) {
+                Slog.e(TAG, "Exception when getting locales for package: " + appInfo.packageName,
+                        e);
+            }
+        }
+
+        if (pkgStates.isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Final payload=null");
+            }
+            // Returning null here will ensure deletion of the entry for LMS from the backup data.
+            return null;
+        }
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            // Passing arbitrary value for creationTimeMillis since it is ignored when forStage
+            // is false.
+            writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not write to xml for backup ", e);
+            return null;
+        }
+
+        if (DEBUG) {
+            try {
+                Slog.d(TAG, "Final payload=" + out.toString("UTF-8"));
+            } catch (UnsupportedEncodingException e) {
+                Slog.w(TAG, "Could not encode payload to UTF-8", e);
+            }
+        }
+        return out.toByteArray();
+    }
+
+    private void cleanStagedDataForOldEntriesLocked() {
+        for (int i = 0; i < mStagedData.size(); i++) {
+            int userId = mStagedData.keyAt(i);
+            StagedData stagedData = mStagedData.get(userId);
+            if (stagedData.mCreationTimeMillis
+                    < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) {
+                deleteStagedDataLocked(userId);
+            }
+        }
+    }
+
+    /**
+     * @see LocaleManagerInternal#stageAndApplyRestoredPayload(byte[] payload, int userId)
+     */
+    public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "stageAndApplyRestoredPayload user=" + userId + " payload="
+                    + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null));
+        }
+        if (payload == null) {
+            Slog.e(TAG, "stageAndApplyRestoredPayload: no payload to restore for user " + userId);
+            return;
+        }
+
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
+
+        HashMap<String, String> pkgStates = new HashMap<>();
+        try {
+            // Parse the input blob into a list of BackupPackageState.
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+            XmlUtils.beginDocument(parser, LOCALES_XML_TAG);
+            pkgStates = readFromXml(parser);
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(TAG, "Could not parse payload ", e);
+        }
+
+        // We need a lock here to prevent race conditions when accessing the stage file.
+        // It might happen that a restore was triggered (manually using bmgr cmd) and at the same
+        // time a new package is added. We want to ensure that both these operations aren't
+        // performed simultaneously.
+        synchronized (mStagedDataLock) {
+            // Backups for apps which are yet to be installed.
+            mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>()));
+
+            for (String pkgName : pkgStates.keySet()) {
+                String languageTags = pkgStates.get(pkgName);
+                // Check if the application is already installed for the concerned user.
+                if (isPackageInstalledForUser(pkgName, userId)) {
+                    // Don't apply the restore if the locales have already been set for the app.
+                    checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+                } else {
+                    // Stage the data if the app isn't installed.
+                    mStagedData.get(userId).mPackageStates.put(pkgName, languageTags);
+                    if (DEBUG) {
+                        Slog.d(TAG, "Add locales=" + languageTags
+                                + " package=" + pkgName + " for lazy restore.");
+                    }
+                }
+            }
+
+            writeStageFileLocked(userId);
+        }
+    }
+
+    /**
+     * Notifies the backup manager to include the "android" package in the next backup pass.
+     */
+    public void notifyBackupManager() {
+        BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
+    }
+
+    private boolean isPackageInstalledForUser(String packageName, int userId) {
+        PackageInfo pkgInfo = null;
+        try {
+            pkgInfo = mContext.getPackageManager().getPackageInfoAsUser(
+                    packageName, /* flags= */ 0, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Could not get package info for " + packageName, e);
+            }
+        }
+        return pkgInfo != null;
+    }
+
+    /**
+     * Checks if locales already exist for the application and applies the restore accordingly.
+     * <p>
+     * The user might change the locales for an application before the restore is applied. In this
+     * case, we want to keep the user settings and discard the restore.
+     */
+    private void checkExistingLocalesAndApplyRestore(@NonNull String pkgName,
+            @NonNull String languageTags, int userId) {
+        try {
+            LocaleList currLocales = mLocaleManagerService.getApplicationLocales(
+                    pkgName,
+                    userId);
+            if (!currLocales.isEmpty()) {
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Could not check for current locales before restoring", e);
+        }
+
+        // Restore the locale immediately
+        try {
+            mLocaleManagerService.setApplicationLocales(pkgName, userId,
+                    LocaleList.forLanguageTags(languageTags));
+            if (DEBUG) {
+                Slog.d(TAG, "Restored locales=" + languageTags + " for package=" + pkgName);
+            }
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Could not restore locales for " + pkgName, e);
+        }
+    }
+
+    /**
+     * Converts the list of app backups into xml and writes it onto the disk.
+     */
+    private void writeStageFileLocked(int userId) {
+        StagedData stagedData = mStagedData.get(userId);
+        if (stagedData.mPackageStates.isEmpty()) {
+            deleteStagedDataLocked(userId);
+            return;
+        }
+
+        final FileOutputStream stagedDataOutputStream;
+        AtomicFile stageFile = new AtomicFile(
+                new File(mStagedLocalesDir,
+                        TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)));
+        try {
+            stagedDataOutputStream = stageFile.startWrite();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to save stage file");
+            return;
+        }
+
+        try {
+            writeToXml(stagedDataOutputStream, stagedData.mPackageStates,  /* forStage= */ true,
+                    stagedData.mCreationTimeMillis);
+            stageFile.finishWrite(stagedDataOutputStream);
+            if (DEBUG) {
+                Slog.d(TAG, "Stage file written.");
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not write stage file", e);
+            stageFile.failWrite(stagedDataOutputStream);
+        }
+    }
+
+    private void deleteStagedDataLocked(@UserIdInt int userId) {
+        AtomicFile stageFile = getStageFileIfExistsLocked(userId);
+        if (stageFile != null) {
+            stageFile.delete();
+        }
+        mStagedData.remove(userId);
+    }
+
+    private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) {
+        final File stageFile = new File(mStagedLocalesDir,
+                TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId));
+        return stageFile.isFile() ? new AtomicFile(stageFile)
+                : null;
+    }
+
+    /**
+     * Parses the backup data from the serialized xml input stream.
+     */
+    private @NonNull HashMap<String, String> readFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        HashMap<String, String> packageStates = new HashMap<>();
+        int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            if (parser.getName().equals(PACKAGE_XML_TAG)) {
+                String packageName = parser.getAttributeValue(/* namespace= */ null,
+                        ATTR_PACKAGE_NAME);
+                String languageTags = parser.getAttributeValue(/* namespace= */ null, ATTR_LOCALES);
+
+                if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) {
+                    packageStates.put(packageName, languageTags);
+                }
+            }
+        }
+        return packageStates;
+    }
+
+    /**
+     * Converts the list of app backup data into a serialized xml stream.
+     *
+     * @param forStage Flag to indicate whether this method is called for the purpose of
+     * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because
+     * we only need it for the stage data.
+     * @param creationTimeMillis The timestamp when the stage data was created. This is required
+     * to determine when to delete the stage data.
+     */
+    private static void writeToXml(OutputStream stream,
+            @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis)
+            throws IOException {
+        if (pkgStates.isEmpty()) {
+            // No need to write anything at all if pkgStates is empty.
+            return;
+        }
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(stream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(/* namespace= */ null, LOCALES_XML_TAG);
+
+        if (forStage) {
+            out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS,
+                    Long.toString(creationTimeMillis));
+        }
+
+        for (String pkg : pkgStates.keySet()) {
+            out.startTag(/* namespace= */ null, PACKAGE_XML_TAG);
+            out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg);
+            out.attribute(/* namespace= */ null, ATTR_LOCALES, pkgStates.get(pkg));
+            out.endTag(/*namespace= */ null, PACKAGE_XML_TAG);
+        }
+
+        out.endTag(/* namespace= */ null, LOCALES_XML_TAG);
+        out.endDocument();
+    }
+
+    private static class StagedData {
+        final long mCreationTimeMillis;
+        final HashMap<String, String> mPackageStates;
+
+        StagedData(long creationTimeMillis, HashMap<String, String> pkgStates) {
+            mCreationTimeMillis = creationTimeMillis;
+            mPackageStates = pkgStates;
+        }
+    }
+
+    /**
+     * Broadcast listener to capture user removed event.
+     *
+     * <p>The stage file is deleted when a user is removed.
+     */
+    private final class UserMonitor extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_USER_REMOVED)) {
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    synchronized (mStagedDataLock) {
+                        deleteStagedDataLocked(userId);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in user monitor.", e);
+            }
+        }
+    }
+
+    /**
+     * Helper to monitor package states.
+     *
+     * <p>We're interested in package added, package data cleared and package removed events.
+     */
+    private final class PackageMonitorImpl extends PackageMonitor {
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            try {
+                synchronized (mStagedDataLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    if (mStagedData.contains(userId)) {
+                        // Perform lazy restore only if the staged data exists.
+                        doLazyRestoreLocked(packageName, userId);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageAdded.", e);
+            }
+        }
+
+        @Override
+        public void onPackageDataCleared(String packageName, int uid) {
+            try {
+                notifyBackupManager();
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageDataCleared.", e);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            try {
+                notifyBackupManager();
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageRemoved.", e);
+            }
+        }
+    }
+
+    /**
+     * Performs lazy restore from the staged data.
+     *
+     * <p>This is invoked by the package monitor on the package added callback.
+     */
+    private void doLazyRestoreLocked(String packageName, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "doLazyRestore package=" + packageName + " user=" + userId);
+        }
+
+        // Check if the package is installed indeed
+        if (!isPackageInstalledForUser(packageName, userId)) {
+            Slog.e(TAG, packageName + " not installed for user " + userId
+                    + ". Could not restore locales from stage file");
+            return;
+        }
+
+        StagedData stagedData = mStagedData.get(userId);
+        for (String pkgName : stagedData.mPackageStates.keySet()) {
+            String languageTags = stagedData.mPackageStates.get(pkgName);
+
+            if (pkgName.equals(packageName)) {
+
+                checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+
+                // Remove the restored entry from the staged data list.
+                stagedData.mPackageStates.remove(pkgName);
+                // Update the file on the disk.
+                writeStageFileLocked(userId);
+
+                // No need to loop further after restoring locales because the staged data will
+                // contain at most one entry for the newly added package.
+                break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerInternal.java b/services/core/java/com/android/server/locales/LocaleManagerInternal.java
new file mode 100644
index 0000000..2db92a5
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.locales;
+
+import android.annotation.Nullable;
+
+/**
+ * System-server internal interface to the {@link LocaleManagerService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class LocaleManagerInternal {
+    /**
+     * Returns the app-specific locales to be backed up as a data-blob.
+     */
+    public abstract @Nullable byte[] getBackupPayload(int userId);
+
+    /**
+     * Restores the app-locales that were previously backed up.
+     *
+     * <p>This method will parse the input data blob and restore the locales for apps which are
+     * present on the device. It will stage the locale data for the apps which are not installed
+     * at the time this is called, to be referenced later when the app is installed.
+     */
+    public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 457c2fd..6aabdb5 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ILocaleManager;
@@ -29,6 +30,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.LocaleList;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -51,11 +53,14 @@
  */
 public class LocaleManagerService extends SystemService {
     private static final String TAG = "LocaleManagerService";
-    private final Context mContext;
+    final Context mContext;
     private final LocaleManagerService.LocaleManagerBinderService mBinderService;
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
     private PackageManagerInternal mPackageManagerInternal;
+
+    private LocaleManagerBackupHelper mBackupHelper;
+
     public static final boolean DEBUG = false;
 
     public LocaleManagerService(Context context) {
@@ -65,23 +70,48 @@
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mBackupHelper = new LocaleManagerBackupHelper(this,
+                mPackageManagerInternal);
     }
 
     @VisibleForTesting
     LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
-            PackageManagerInternal packageManagerInternal) {
+            PackageManagerInternal packageManagerInternal,
+            LocaleManagerBackupHelper localeManagerBackupHelper) {
         super(context);
         mContext = context;
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = activityTaskManagerInternal;
         mActivityManagerInternal = activityManagerInternal;
         mPackageManagerInternal = packageManagerInternal;
+        mBackupHelper = localeManagerBackupHelper;
     }
 
     @Override
     public void onStart() {
         publishBinderService(Context.LOCALE_SERVICE, mBinderService);
+        LocalServices.addService(LocaleManagerInternal.class, new LocaleManagerInternalImpl());
+    }
+
+    private final class LocaleManagerInternalImpl extends LocaleManagerInternal {
+
+        @Override
+        public @Nullable byte[] getBackupPayload(int userId) {
+            checkCallerIsSystem();
+            return mBackupHelper.getBackupPayload(userId);
+        }
+
+        @Override
+        public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
+            mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
+        }
+
+        private void checkCallerIsSystem() {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Caller is not system.");
+            }
+        }
     }
 
     private final class LocaleManagerBinderService extends ILocaleManager.Stub {
@@ -110,6 +140,7 @@
             (new LocaleManagerShellCommand(mBinderService))
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
+
     }
 
     /**
@@ -161,6 +192,8 @@
             notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyRegisteredReceivers(appPackageName, userId, locales);
+
+            mBackupHelper.notifyBackupManager();
         }
     }
 
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 22a675a..5e38bca 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -23,6 +23,8 @@
 import static com.android.server.location.LocationManagerService.TAG;
 import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
+import static java.lang.Math.max;
+
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
@@ -53,6 +55,7 @@
         implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
 
     private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+    private static final long MIN_INTERVAL_MS = 1000;
 
     final Object mLock = new Object();
 
@@ -179,7 +182,7 @@
                 && mLastLocation != null
                 && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
                 <= MAX_STATIONARY_LOCATION_AGE_MS) {
-            throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+            throttlingIntervalMs = max(mIncomingRequest.getIntervalMillis(), MIN_INTERVAL_MS);
         }
 
         ProviderRequest newRequest;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index e64ec77..99cb6f0 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.permission.IPermissionManager;
 import android.util.ArrayMap;
@@ -73,7 +74,12 @@
      */
     public boolean hasPermission(int uid) {
         assertFlag();
-        return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     /**
@@ -185,27 +191,37 @@
 
     public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
         assertFlag();
+        final long callingId = Binder.clearCallingIdentity();
         try {
-            int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                    userId);
-            return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                    || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not reach system server", e);
+            try {
+                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+                        userId);
+                return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                        || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not reach system server", e);
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
-        return false;
     }
 
     boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
         assertFlag();
+        final long callingId = Binder.clearCallingIdentity();
         try {
-            int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                    userId);
-            return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not reach system server", e);
+            try {
+                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+                        userId);
+                return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not reach system server", e);
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
-        return false;
     }
 
     private void assertFlag() {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 328a55f..0f3b4bc 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -143,8 +143,9 @@
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_CHANGE_WIFI_STATE,
             UserManager.DISALLOW_WIFI_TETHERING,
-            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI
-
+            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -190,7 +191,9 @@
             UserManager.DISALLOW_MICROPHONE_TOGGLE,
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_CHANGE_WIFI_STATE,
-            UserManager.DISALLOW_WIFI_TETHERING
+            UserManager.DISALLOW_WIFI_TETHERING,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     );
 
     /**
@@ -227,7 +230,9 @@
                     UserManager.DISALLOW_CONFIG_DATE_TIME,
                     UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
                     UserManager.DISALLOW_CHANGE_WIFI_STATE,
-                    UserManager.DISALLOW_WIFI_TETHERING
+                    UserManager.DISALLOW_WIFI_TETHERING,
+                    UserManager.DISALLOW_WIFI_DIRECT,
+                    UserManager.DISALLOW_ADD_WIFI_CONFIG
     );
 
     /**
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 6628802..e508260 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -243,21 +243,24 @@
 
         @Override
         public boolean requestFrontend(@NonNull TunerFrontendRequest request,
-                @NonNull int[] frontendHandle) throws RemoteException {
+                @NonNull int[] frontendHandle) {
             enforceTunerAccessPermission("requestFrontend");
             enforceTrmAccessPermission("requestFrontend");
             if (frontendHandle == null) {
-                throw new RemoteException("frontendHandle can't be null");
+                Slog.e(TAG, "frontendHandle can't be null");
+                return false;
             }
             synchronized (mLock) {
                 if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request frontend from unregistered client: "
+                    Slog.e(TAG, "Request frontend from unregistered client: "
                             + request.clientId);
+                    return false;
                 }
                 // If the request client is holding or sharing a frontend, throw an exception.
                 if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
-                    throw new RemoteException("Release frontend before requesting another one. "
-                            + "Client id: " + request.clientId);
+                    Slog.e(TAG, "Release frontend before requesting another one. Client id: "
+                            + request.clientId);
+                    return false;
                 }
                 return requestFrontendInternal(request, frontendHandle);
             }
@@ -1153,7 +1156,8 @@
             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
             if (ownerClient != null) {
                 for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
-                    clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+                    reclaimResource(shareOwnerId,
+                            TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 7b26fe0..4576957 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -226,9 +226,9 @@
                         .getSystemService(TelephonyManager.class)
                         .createForSubscriptionId(subId);
 
-        if (!networkPriority.getAllowedPlmnIds().isEmpty()) {
+        if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) {
             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
-            if (!networkPriority.getAllowedPlmnIds().contains(plmnId)) {
+            if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b28478..447f4be 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -157,6 +157,7 @@
 import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
 import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED;
 import static com.android.server.wm.ActivityRecordProto.PROC_ID;
+import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
 import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
@@ -258,6 +259,7 @@
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ConstrainDisplayApisConfig;
 import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -585,6 +587,8 @@
      */
     private CompatDisplayInsets mCompatDisplayInsets;
 
+    private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
+
     boolean pendingVoiceInteractionStart;   // Waiting for activity-invoked voice session
     IVoiceInteractionSession voiceSession;  // Voice interaction session for this activity
 
@@ -1153,8 +1157,10 @@
             if (info.configChanges != 0) {
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
-            pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis());
-            pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis());
+            pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis(
+                    sConstrainDisplayApisConfig));
+            pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis(
+                    sConstrainDisplayApisConfig));
         }
         if (mLastParentBeforePip != null) {
             pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
@@ -1731,6 +1737,10 @@
             info.windowLayout.windowLayoutAffinity =
                     uid + ":" + info.windowLayout.windowLayoutAffinity;
         }
+        // Initialize once, when we know all system services are available.
+        if (sConstrainDisplayApisConfig == null) {
+            sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig();
+        }
         stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0;
         nonLocalizedLabel = aInfo.nonLocalizedLabel;
         labelRes = aInfo.labelRes;
@@ -7330,8 +7340,8 @@
                                 + "should create compatDisplayInsets = %s",
                         getUid(),
                         mTmpBounds,
-                        info.neverSandboxDisplayApis(),
-                        info.alwaysSandboxDisplayApis(),
+                        info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
+                        info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                         !matchParentBounds(),
                         mCompatDisplayInsets != null,
                         shouldCreateCompatDisplayInsets());
@@ -7892,11 +7902,11 @@
             return false;
         }
         // Never apply sandboxing to an app that should be explicitly excluded from the config.
-        if (info != null && info.neverSandboxDisplayApis()) {
+        if (info.neverSandboxDisplayApis(sConstrainDisplayApisConfig)) {
             return false;
         }
         // Always apply sandboxing to an app that should be explicitly included from the config.
-        if (info != null && info.alwaysSandboxDisplayApis()) {
+        if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) {
             return true;
         }
         // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
@@ -8899,6 +8909,9 @@
         proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
         proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
         proto.write(MIN_ASPECT_RATIO, getMinAspectRatio());
+        // Only record if max bounds sandboxing is applied, if the caller has the necessary
+        // permission to access the device configs.
+        proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 471b4ce..ba96590 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -72,6 +72,8 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -1288,7 +1290,10 @@
 
         // This is used to block background activity launch even if the app is still
         // visible to user after user clicking home button.
-        final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
+        final int appSwitchState = mService.getBalAppSwitchesState();
+        final boolean appSwitchAllowed = appSwitchState == APP_SWITCH_ALLOW;
+        final boolean appSwitchAllowedOrFg =
+                appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
 
         // don't abort if the callingUid has a visible window or is a persistent system process
         final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
@@ -1301,7 +1306,7 @@
 
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
-        if (((appSwitchAllowed || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
+        if (((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
                 && callingUidHasAnyVisibleWindow)
                 || isCallingUidPersistentSystemProcess) {
             if (DEBUG_ACTIVITY_STARTS) {
@@ -1458,7 +1463,7 @@
         // anything that has fallen through would currently be aborted
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
-                + "; appSwitchAllowed: " + appSwitchAllowed
+                + "; appSwitchState: " + appSwitchState
                 + "; isCallingUidForeground: " + isCallingUidForeground
                 + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
                 + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index bbccac4..be01646 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -505,7 +505,27 @@
      * Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
      * disables this.
      */
-    private volatile boolean mAppSwitchesAllowed = true;
+    private volatile int mAppSwitchesState = APP_SWITCH_ALLOW;
+
+    // The duration of resuming foreground app switch from disallow.
+    private static final long RESUME_FG_APP_SWITCH_MS = 500;
+
+    /** App switch is not allowed. */
+    static final int APP_SWITCH_DISALLOW = 0;
+
+    /** App switch is allowed only if the activity launch was requested by a foreground app. */
+    static final int APP_SWITCH_FG_ONLY = 1;
+
+    /** App switch is allowed. */
+    static final int APP_SWITCH_ALLOW = 2;
+
+    @IntDef({
+            APP_SWITCH_DISALLOW,
+            APP_SWITCH_FG_ONLY,
+            APP_SWITCH_ALLOW,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AppSwitchState {}
 
     /**
      * Last stop app switches time, apps finished before this time cannot start background activity
@@ -1250,7 +1270,7 @@
             if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null
                     && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid
                     == Binder.getCallingUid()) {
-                mAppSwitchesAllowed = true;
+                mAppSwitchesState = APP_SWITCH_ALLOW;
             }
         }
         return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
@@ -2169,8 +2189,8 @@
     /**
      * Return true if app switching is allowed.
      */
-    boolean getBalAppSwitchesAllowed() {
-        return mAppSwitchesAllowed;
+    @AppSwitchState int getBalAppSwitchesState() {
+        return mAppSwitchesState;
     }
 
     /** Register an {@link AnrController} to control the ANR dialog behavior */
@@ -3691,8 +3711,10 @@
     public void stopAppSwitches() {
         mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowed = false;
+            mAppSwitchesState = APP_SWITCH_DISALLOW;
             mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
+            mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
+            mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS);
         }
     }
 
@@ -3700,7 +3722,8 @@
     public void resumeAppSwitches() {
         mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowed = true;
+            mAppSwitchesState = APP_SWITCH_ALLOW;
+            mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
         }
     }
 
@@ -5193,6 +5216,7 @@
         static final int REPORT_TIME_TRACKER_MSG = 1;
         static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
         static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
+        static final int RESUME_FG_APP_SWITCH_MSG = 4;
 
         static final int FIRST_ACTIVITY_TASK_MSG = 100;
         static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5230,6 +5254,14 @@
                     }
                 }
                 break;
+                case RESUME_FG_APP_SWITCH_MSG: {
+                    synchronized (mGlobalLock) {
+                        if (mAppSwitchesState == APP_SWITCH_DISALLOW) {
+                            mAppSwitchesState = APP_SWITCH_FG_ONLY;
+                        }
+                    }
+                }
+                break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 71ab5b6..4768b27 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -846,20 +845,6 @@
     }
 
     /**
-     * Only trusted overlays are allowed to use FLAG_SLIPPERY.
-     */
-    static int sanitizeFlagSlippery(int flags, int privateFlags, String name) {
-        if ((flags & FLAG_SLIPPERY) == 0) {
-            return flags;
-        }
-        if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
-            return flags;
-        }
-        Slog.w(TAG, "Removing FLAG_SLIPPERY for non-trusted overlay " + name);
-        return flags & ~FLAG_SLIPPERY;
-    }
-
-    /**
      * Sanitize the layout parameters coming from a client.  Allows the policy
      * to do things like ensure that windows of a specific type can't take
      * input focus.
@@ -942,8 +927,6 @@
         } else if (mRoundedCornerWindow == win) {
             mRoundedCornerWindow = null;
         }
-
-        attrs.flags = sanitizeFlagSlippery(attrs.flags, attrs.privateFlags, win.getName());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ea99781..3e55811 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3115,14 +3115,6 @@
     }
 
     @Override
-    boolean fillsParent() {
-        // From the perspective of policy, we still want to report that this task fills parent
-        // in fullscreen windowing mode even it doesn't match parent bounds because there will be
-        // letterbox around its real content.
-        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
-    }
-
-    @Override
     void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
         final int count = mChildren.size();
         boolean isLeafTask = true;
@@ -4534,14 +4526,15 @@
             }
             super.setWindowingMode(windowingMode);
 
-            // Try reparent pinned activity back to its original task after onConfigurationChanged
-            // cascade finishes. This is done on Task level instead of
-            // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
-            // we set final windowing mode on the ActivityRecord first and then on its Task when
-            // the exit PiP transition finishes. Meanwhile, the exit transition is always
-            // performed on its original task, reparent immediately in ActivityRecord breaks it.
-            if (currentMode == WINDOWING_MODE_PINNED) {
-                if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+            if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
+                // Try reparent pinned activity back to its original task after
+                // onConfigurationChanged cascade finishes. This is done on Task level instead of
+                // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit
+                // PiP, we set final windowing mode on the ActivityRecord first and then on its
+                // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
+                // always performed on its original task, reparent immediately in ActivityRecord
+                // breaks it.
+                if (topActivity.getLastParentBeforePip() != null) {
                     // Do not reparent if the pinned task is in removal, indicated by the
                     // force hidden flag.
                     if (!isForceHidden()) {
@@ -4554,6 +4547,11 @@
                         }
                     }
                 }
+                // Resume app-switches-allowed flag when exiting from pinned mode since
+                // it does not follow the ActivityStarter path.
+                if (topActivity.shouldBeVisible()) {
+                    mAtmService.resumeAppSwitches();
+                }
             }
 
             if (creating) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fdaa2fc..c845dca 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2342,6 +2342,14 @@
         return true;
     }
 
+    @Override
+    boolean fillsParent() {
+        // From the perspective of policy, we still want to report that this task fills parent
+        // in fullscreen windowing mode even it doesn't match parent bounds because there will be
+        // letterbox around its real content.
+        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
+    }
+
     boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
             boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
         boolean printed = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e986fd2..58860de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1652,6 +1652,7 @@
 
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
             displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
+            attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
             win.setRequestedVisibilities(requestedVisibilities);
 
             res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
@@ -2207,6 +2208,7 @@
             if (attrs != null) {
                 displayPolicy.adjustWindowParamsLw(win, attrs);
                 win.mToken.adjustWindowParams(win, attrs);
+                attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
                 int disableFlags =
                         (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK;
                 if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
@@ -8220,6 +8222,23 @@
     }
 
     /**
+     * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+     */
+    private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+        if ((flags & FLAG_SLIPPERY) == 0) {
+            return flags;
+        }
+        final int permissionResult = mContext.checkPermission(
+                    android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+        if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+            Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+                    + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+            return flags & ~FLAG_SLIPPERY;
+        }
+        return flags;
+    }
+
+    /**
      * Assigns an InputChannel to a SurfaceControl and configures it to receive
      * touch input according to it's on-screen geometry.
      *
@@ -8258,7 +8277,7 @@
         h.setWindowToken(window);
         h.name = name;
 
-        flags = DisplayPolicy.sanitizeFlagSlippery(flags, privateFlags, name);
+        flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
 
         final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
                 | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1cfbe07..90a5d69e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -37,6 +37,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 
@@ -512,7 +513,7 @@
      */
     @HotPath(caller = HotPath.START_SERVICE)
     public boolean areBackgroundFgsStartsAllowed() {
-        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(),
+        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState() == APP_SWITCH_ALLOW,
                 true /* isCheckingForFgsStart */);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
index 17d7c51..d6db1b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
@@ -123,6 +123,8 @@
 
         doNothing().when(mContextSpy).enforceCallingPermission(
                 eq(Manifest.permission.WRITE_COMMUNAL_STATE), anyString());
+        doNothing().when(mContextSpy).enforceCallingPermission(
+                eq(Manifest.permission.READ_COMMUNAL_STATE), anyString());
 
         mService = new CommunalManagerService(mContextSpy);
         mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
@@ -203,6 +205,18 @@
     }
 
     @Test
+    public void testIsCommunalMode_isTrue() throws RemoteException {
+        mBinder.setCommunalViewShowing(true);
+        assertThat(mBinder.isCommunalMode()).isTrue();
+    }
+
+    @Test
+    public void testIsCommunalMode_isFalse() throws RemoteException {
+        mBinder.setCommunalViewShowing(false);
+        assertThat(mBinder.isCommunalMode()).isFalse();
+    }
+
+    @Test
     public void testIntercept_unlocked_communalOff_appNotEnabled_showWhenLockedOff() {
         when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
         mAInfo.flags = 0;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4d6f49e..4eba219 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,6 +90,19 @@
     }
 
     @Test
+    public void testThrottle_lowInterval() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(0).build();
+
+        mProvider.getController().setRequest(request);
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mListener, after(1500).times(2)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
     public void testThrottle_stationaryExit() {
         ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
 
@@ -104,17 +117,16 @@
 
         mInjector.getDeviceIdleHelper().setIdle(true);
         verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
-        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceStationaryHelper().setStationary(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
     public void testThrottle_idleExit() {
-        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
 
         mProvider.getController().setRequest(request);
         verify(mDelegate).onSetRequest(request);
@@ -127,17 +139,16 @@
 
         mInjector.getDeviceStationaryHelper().setStationary(true);
         verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
-        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceIdleHelper().setIdle(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
     public void testThrottle_NoInitialLocation() {
-        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
 
         mProvider.getController().setRequest(request);
         verify(mDelegate).onSetRequest(request);
@@ -149,11 +160,11 @@
         mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
         verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
         verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceStationaryHelper().setStationary(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 2eb9e34..a0d86c9 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -187,6 +187,30 @@
     }
 
     @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_enabled() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(4000);
+        assertEquals(4000,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_disabled() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(0);
+        assertEquals(0,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_cappedAtMaximum() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(10000);
+        assertEquals(GestureLauncherService.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
     public void testHandleCameraLaunchGesture_userSetupComplete() {
         withUserSetupCompleteValue(true);
 
@@ -645,6 +669,211 @@
     }
 
     @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_singleTaps_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent single tap is intercepted, but should not trigger any gesture
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Another single tap should be the same (intercepted but should not trigger gesture)
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void
+    testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() {
+        // Enable camera double tap gesture
+        withCameraDoubleTapPowerEnableConfigValue(true);
+        withCameraDoubleTapPowerDisableSettingValue(0);
+        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent double tap is intercepted, but should not trigger any gesture
+        for (int i = 0; i < 2; i++) {
+            KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION,
+                    IGNORED_CODE, IGNORED_REPEAT);
+            boolean interactive = true;
+            MutableBoolean outLaunched = new MutableBoolean(true);
+            boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent,
+                    interactive, outLaunched);
+            assertTrue(intercepted);
+            assertFalse(outLaunched.value);
+            interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_fiveTaps_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent 5 taps are intercepted, but should not trigger any gesture
+        for (int i = 0; i < 5; i++) {
+            KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION,
+                    IGNORED_CODE, IGNORED_REPEAT);
+            boolean interactive = true;
+            MutableBoolean outLaunched = new MutableBoolean(true);
+            boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent,
+                    interactive, outLaunched);
+            assertTrue(intercepted);
+            assertFalse(outLaunched.value);
+            interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_longPress_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent long press is intercepted, but should not trigger any gesture
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_cooldownDisabled_cooldownNotTriggered() {
+        // Disable power button cooldown by setting cooldown period to 0
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(0);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent single tap is NOT intercepted
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Long press also NOT intercepted
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void
+    testInterceptPowerKeyDown_triggerEmergency_outsideCooldownPeriod_cooldownNotTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(5000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to be outside of cooldown period
+        long interval = 5001;
+        eventTime += interval;
+
+        // Subsequent single tap is NOT intercepted
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Long press also NOT intercepted
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
     public void testInterceptPowerKeyDown_longpress() {
         withCameraDoubleTapPowerEnableConfigValue(true);
         withCameraDoubleTapPowerDisableSettingValue(0);
@@ -1153,6 +1382,45 @@
         assertEquals(1, tapCounts.get(1).intValue());
     }
 
+    /**
+     * Helper method to trigger emergency gesture by pressing button for 5 times.
+     * @return last event time.
+     */
+    private long triggerEmergencyGesture() {
+        // Enable emergency power gesture
+        withEmergencyGestureEnabledConfigValue(true);
+        withEmergencyGestureEnabledSettingValue(true);
+        mGestureLauncherService.updateEmergencyGestureEnabled();
+        withUserSetupCompleteValue(true);
+
+        // 4 button presses
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        boolean interactive = true;
+        KeyEvent keyEvent;
+        MutableBoolean outLaunched = new MutableBoolean(false);
+        for (int i = 0; i < 4; i++) {
+            keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                    IGNORED_REPEAT);
+            mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched);
+            final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+
+        // 5th button press should trigger the emergency flow
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        outLaunched.value = false;
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(outLaunched.value);
+        assertTrue(intercepted);
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+        verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+
+        return eventTime;
+    }
+
     private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) {
         when(mResources.getBoolean(
                 com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled))
@@ -1181,6 +1449,14 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private void withEmergencyGesturePowerButtonCooldownPeriodMsValue(int period) {
+        Settings.Secure.putIntForUser(
+                mContentResolver,
+                Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                period,
+                UserHandle.USER_CURRENT);
+    }
+
     private void withUserSetupCompleteValue(boolean userSetupComplete) {
         int userSetupCompleteValue = userSetupComplete ? 1 : 0;
         Settings.Secure.putIntForUser(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
index 8765c9a..6a2192a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
@@ -1,7 +1 @@
-set noparent
-
-kchyn@google.com
-jaggies@google.com
-curtislb@google.com
-ilyamaty@google.com
-joshmccloskey@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index f664517..abe7d89 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -21,7 +21,9 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -63,6 +65,7 @@
 
     @Mock SensorManager mSensorManager;
     @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+    @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
     @Mock HysteresisLevels mAmbientBrightnessThresholds;
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock Handler mNoOpHandler;
@@ -100,7 +103,7 @@
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
-                mContext, mHbmController
+                mContext, mHbmController, mIdleBrightnessMappingStrategy
         );
 
         when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
@@ -231,4 +234,44 @@
         // There should be a user data point added to the mapper.
         verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
     }
+
+    @Test
+    public void testSwitchToIdleMappingStrategy() throws Exception {
+        Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+        mController = setupController(lightSensor);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
+
+        // User sets brightness to 100
+        mController.configure(true /* enable */, null /* configuration */,
+                0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+                false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+        // There should be a user data point added to the mapper.
+        verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
+        verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
+
+        // Now let's do the same for idle mode
+        mController.switchToIdleMode();
+        // Called once for init, and once when switching
+        verify(mBrightnessMappingStrategy, times(2)).isForIdleMode();
+        // Ensure, after switching, original BMS is not used anymore
+        verifyNoMoreInteractions(mBrightnessMappingStrategy);
+
+        // User sets idle brightness to 0.5
+        mController.configure(true /* enable */, null /* configuration */,
+                0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+                false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+        // Ensure we use the correct mapping strategy
+        verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 68e90fb..eaa271a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -219,7 +219,7 @@
         builder.setUniqueId(uniqueId);
         builder.setFlags(flags);
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -341,7 +341,7 @@
         builder.setFlags(flags);
         builder.setUniqueId(uniqueId);
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -580,7 +580,8 @@
                 VIRTUAL_DISPLAY_NAME, width, height, dpi);
         builder.setUniqueId(uniqueId);
         final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, null /* projection */, null /* virtualDeviceToken */,
+                PACKAGE_NAME);
 
         // The second virtual display requests to mirror the first virtual display.
         final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -590,7 +591,8 @@
         builder2.setUniqueId(uniqueId2);
         builder2.setDisplayIdToMirror(firstDisplayId);
         final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken2 /* callback */, null /* projection */,
+                null /* virtualDeviceToken */, PACKAGE_NAME);
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
         // flush the handler
@@ -628,7 +630,8 @@
         builder.setSurface(surface);
         builder.setUniqueId(uniqueId);
         final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, null /* projection */, null /* virtualDeviceToken */,
+                PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -1108,7 +1111,7 @@
         builder.setUniqueId(uniqueId);
 
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
         DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
new file mode 100644
index 0000000..70e78eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2021 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.locales;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.SimpleClock;
+import android.util.AtomicFile;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for the {@link LocaleManagerInternal}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleManagerBackupRestoreTest {
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
+    private static final String TEST_LOCALES_XML_TAG = "locales";
+    private static final int DEFAULT_USER_ID = 0;
+    private static final int WORK_PROFILE_USER_ID = 10;
+    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
+    private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
+    private static final LocaleList DEFAULT_LOCALES =
+            LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
+    private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
+            DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);
+    private static final File STAGED_LOCALES_DIR = new File(
+            Environment.getExternalStorageDirectory(), "lmsUnitTests");
+
+
+    private LocaleManagerBackupHelper mBackupHelper;
+    private long mCurrentTimeMillis;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private LocaleManagerService mMockLocaleManagerService;
+    BroadcastReceiver mUserMonitor;
+    PackageMonitor mPackageMonitor;
+
+    private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
+        @Override
+        public long millis() {
+            return currentTimeMillis();
+        }
+    };
+
+    private long currentTimeMillis() {
+        return mCurrentTimeMillis;
+    }
+
+    private void setCurrentTimeMillis(long currentTimeMillis) {
+        mCurrentTimeMillis = currentTimeMillis;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
+        mMockLocaleManagerService = mock(LocaleManagerService.class);
+
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+
+        mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
+                mMockLocaleManagerService, mMockPackageManagerInternal,
+                new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"), mClock));
+        doNothing().when(mBackupHelper).notifyBackupManager();
+
+        mUserMonitor = mBackupHelper.getUserMonitor();
+        mPackageMonitor = mBackupHelper.getPackageMonitor();
+        setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
+        cleanStagedFiles();
+    }
+
+    @Test
+    public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_noAppLocalesSet_returnsNull() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_appLocalesSet_returnsNonNullBlob() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+
+        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+    }
+
+    @Test
+    public void testBackupPayload_exceptionInGetLocalesAllPackages_returnsNull() throws Exception {
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
+                anyString(), anyInt());
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_exceptionInGetLocalesSomePackages_appsWithExceptionNotBackedUp()
+            throws Exception {
+        // Set up two apps.
+        ApplicationInfo defaultAppInfo = new ApplicationInfo();
+        ApplicationInfo anotherAppInfo = new ApplicationInfo();
+        defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
+        anotherAppInfo.packageName = "com.android.anotherapp";
+        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        // Exception when getting locales for anotherApp.
+        doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
+                eq(anotherAppInfo.packageName), anyInt());
+
+        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+    }
+
+    @Test
+    public void testRestore_nullPayload_nothingRestoredAndNoStageFile() throws Exception {
+        mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ null, DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageFile() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ out.toByteArray(),
+                DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_noStageFileCreated() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES);
+
+        // Stage file wasn't created.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_noAppsInstalled_everythingStaged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                getStageFileIfExists(DEFAULT_USER_ID), DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_someAppsInstalled_partiallyStaged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru", langTagsB = "hi,fr";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        writeTestPayload(out, pkgLocalesMap);
+
+        setUpPackageInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA));
+
+        pkgLocalesMap.remove(pkgNameA);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageFile() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Since locales are already set, we should not restore anything for it.
+        verifyNothingRestored();
+        // Stage file wasn't created
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_appLocalesSetForSomeApps_restoresOnlyForAppsHavingNoLocalesSet()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB", pkgNameC =
+                "com.android.myAppC";
+        String langTagsA = "ru", langTagsB = "hi,fr", langTagsC = "zh,es";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        pkgLocalesMap.put(pkgNameC, langTagsC);
+        writeTestPayload(out, pkgLocalesMap);
+
+        // Both app A & B are installed on the device but A has locales already set.
+        setUpPackageInstalled(pkgNameA);
+        setUpPackageInstalled(pkgNameB);
+        setUpPackageNotInstalled(pkgNameC);
+        setUpLocalesForPackage(pkgNameA, LocaleList.forLanguageTags("mr,fr"));
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameC, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Restore locales only for myAppB.
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
+                any());
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB));
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
+                any());
+
+        // App C is staged.
+        pkgLocalesMap.remove(pkgNameA);
+        pkgLocalesMap.remove(pkgNameB);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_restoreInvokedAgain_creationTimeChanged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
+        setCurrentTimeMillis(newCreationTime);
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                newCreationTime);
+    }
+
+    @Test
+    public void testRestore_appInstalledAfterSUW_restoresFromStage() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru", langTagsB = "hi,fr";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        writeTestPayload(out, pkgLocalesMap);
+
+        setUpPackageNotInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+
+        setUpPackageInstalled(pkgNameA);
+
+        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA));
+
+        pkgLocalesMap.remove(pkgNameA);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        setUpPackageInstalled(pkgNameB);
+
+        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB));
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        // Package is not present on the device when the SUW restore is going on.
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        // App is installed later (post SUW).
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
+
+        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        // Since locales are already set, we should not restore anything for it.
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testStageFileDeletion_backupPassRunAfterRetentionPeriod_stageFileDeleted()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        // Retention period has not elapsed.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should NOT be deleted.
+        checkStageFileExists(DEFAULT_USER_ID);
+
+        // Exactly RETENTION_PERIOD amount of time has passed so stage file should still not be
+        // removed.
+        setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should NOT be deleted.
+        checkStageFileExists(DEFAULT_USER_ID);
+
+        // Retention period has now expired, stage file should be deleted.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should be deleted.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testUserRemoval_userRemoved_stageFileDeleted() throws Exception {
+        final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
+        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
+        String anotherPackage = "com.android.anotherapp";
+        String anotherLangTags = "mr,zh";
+        HashMap<String, String> pkgLocalesMapWorkProfile = new HashMap<>();
+        pkgLocalesMapWorkProfile.put(anotherPackage, anotherLangTags);
+        writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
+
+        // DEFAULT_PACKAGE_NAME is NOT installed on the device.
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+        setUpPackageNotInstalled(anotherPackage);
+
+        mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID);
+        mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(),
+                WORK_PROFILE_USER_ID);
+
+        verifyNothingRestored();
+
+        // Verify stage file contents.
+        AtomicFile stageFileDefaultUser = getStageFileIfExists(DEFAULT_USER_ID);
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, stageFileDefaultUser,
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        AtomicFile stageFileWorkProfile = getStageFileIfExists(WORK_PROFILE_USER_ID);
+        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_USER_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
+        mUserMonitor.onReceive(mMockContext, intent);
+
+        // Stage file should be removed only for DEFAULT_USER_ID.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testLoadStageFiles_invalidNameFormat_stageFileDeleted() throws Exception {
+        // Stage file name should be : staged_locales_<user_id_int>.xml
+        File stageFile = new File(STAGED_LOCALES_DIR, "xyz.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, /* creationTimeMillis= */ 0);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_userIdNotParseable_stageFileDeleted() throws Exception {
+        // Stage file name should be : staged_locales_<user_id_int>.xml
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_abc.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, /* creationTimeMillis= */ 0);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_invalidContent_stageFileDeleted() throws Exception {
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        FileOutputStream out = new FileOutputStream(stageFile);
+        out.write("some_non_xml_string".getBytes());
+        out.close();
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_validContent_doesLazyRestore() throws Exception {
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, DEFAULT_CREATION_TIME_MILLIS);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), DEFAULT_CREATION_TIME_MILLIS);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        mPackageMonitor = mBackupHelper.getPackageMonitor();
+
+        // Stage file still exists.
+        assertTrue(stageFile.isFile());
+
+        // App is installed later.
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+
+        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES);
+
+        // Stage file gets deleted here because all staged locales have been applied.
+        assertFalse(stageFile.isFile());
+    }
+
+    private void setUpPackageInstalled(String packageName) throws Exception {
+        doReturn(new PackageInfo()).when(mMockPackageManager).getPackageInfoAsUser(
+                eq(packageName), anyInt(), anyInt());
+    }
+
+    private void setUpPackageNotInstalled(String packageName) throws Exception {
+        doReturn(null).when(mMockPackageManager).getPackageInfoAsUser(eq(packageName),
+                anyInt(), anyInt());
+    }
+
+    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
+        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(
+                eq(packageName), anyInt());
+    }
+
+    private void setUpDummyAppForPackageManager(String packageName) {
+        ApplicationInfo dummyApp = new ApplicationInfo();
+        dummyApp.packageName = packageName;
+        doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+    }
+
+    /**
+     * Verifies that nothing was restored for any package.
+     *
+     * <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked, we can conclude
+     * that nothing was restored.
+     */
+    private void verifyNothingRestored() throws Exception {
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
+                any());
+    }
+
+
+    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+            byte[] payload)
+            throws IOException, XmlPullParserException {
+        verifyPayloadForAppLocales(expectedPkgLocalesMap, payload, /* forStage= */ false, -1);
+    }
+
+    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+            byte[] payload, boolean forStage, long expectedCreationTime)
+            throws IOException, XmlPullParserException {
+        final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+        Map<String, String> backupDataMap = new HashMap<>();
+        XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
+        if (forStage) {
+            long actualCreationTime = parser.getAttributeLong(/* namespace= */ null,
+                    "creationTimeMillis");
+            assertEquals(expectedCreationTime, actualCreationTime);
+        }
+        int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            if (parser.getName().equals("package")) {
+                String packageName = parser.getAttributeValue(null, "name");
+                String languageTags = parser.getAttributeValue(null, "locales");
+                backupDataMap.put(packageName, languageTags);
+            }
+        }
+
+        assertEquals(expectedPkgLocalesMap, backupDataMap);
+    }
+
+    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
+            throws IOException {
+        writeTestPayload(stream, pkgLocalesMap, /* forStage= */ false, /* creationTimeMillis= */
+                -1);
+    }
+
+    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap,
+            boolean forStage, long creationTimeMillis)
+            throws IOException {
+        if (pkgLocalesMap.isEmpty()) {
+            return;
+        }
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(stream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
+
+        if (forStage) {
+            out.attribute(/* namespace= */ null, "creationTimeMillis",
+                    Long.toString(creationTimeMillis));
+        }
+
+        for (String pkg : pkgLocalesMap.keySet()) {
+            out.startTag(/* namespace= */ null, "package");
+            out.attribute(/* namespace= */ null, "name", pkg);
+            out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg));
+            out.endTag(/*namespace= */ null, "package");
+        }
+
+        out.endTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
+        out.endDocument();
+    }
+
+    private static void verifyStageFileContent(Map<String, String> expectedPkgLocalesMap,
+            AtomicFile stageFile,
+            long creationTimeMillis)
+            throws Exception {
+        assertNotNull(stageFile);
+        try (InputStream stagedDataInputStream = stageFile.openRead()) {
+            verifyPayloadForAppLocales(expectedPkgLocalesMap, stagedDataInputStream.readAllBytes(),
+                    /* forStage= */ true, creationTimeMillis);
+        } catch (IOException | XmlPullParserException e) {
+            throw e;
+        }
+    }
+
+    private static void checkStageFileDoesNotExist(int userId) {
+        assertNull(getStageFileIfExists(userId));
+    }
+
+    private static void checkStageFileExists(int userId) {
+        assertNotNull(getStageFileIfExists(userId));
+    }
+
+    private static AtomicFile getStageFileIfExists(int userId) {
+        File file = new File(STAGED_LOCALES_DIR, String.format("staged_locales_%d.xml", userId));
+        if (file.isFile()) {
+            return new AtomicFile(file);
+        }
+        return null;
+    }
+
+    private static void cleanStagedFiles() {
+        File[] files = STAGED_LOCALES_DIR.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                f.delete();
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index ddc58b2..658f8d5 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.Manifest;
@@ -60,7 +61,7 @@
     private static final int DEFAULT_USER_ID = 0;
     private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
     private static final int INVALID_UID = -1;
-    private static final String DEFAULT_LOCALE_TAGS = "en-XC, ar-XB";
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
     private static final LocaleList DEFAULT_LOCALES =
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
     private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo(
@@ -68,6 +69,7 @@
             /* originatingPackageName = */ null, /* installingPackageName = */ null);
 
     private LocaleManagerService mLocaleManagerService;
+    private LocaleManagerBackupHelper mMockBackupHelper;
 
     @Mock
     private Context mMockContext;
@@ -104,8 +106,9 @@
                 .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
                         anyString(), anyString());
 
+        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
-                mMockActivityManager, mMockPackageManagerInternal);
+                mMockActivityManager, mMockPackageManagerInternal, mMockBackupHelper);
     }
 
     @Test(expected = SecurityException.class)
@@ -122,6 +125,7 @@
             verify(mMockContext).enforceCallingOrSelfPermission(
                     eq(android.Manifest.permission.CHANGE_CONFIGURATION),
                     anyString());
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -133,6 +137,7 @@
                     DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
             fail("Expected NullPointerException");
         } finally {
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -146,6 +151,7 @@
                     /* locales = */ null);
             fail("Expected NullPointerException");
         } finally {
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -163,6 +169,7 @@
                 DEFAULT_LOCALES);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+        verify(mMockBackupHelper, times(1)).notifyBackupManager();
 
     }
 
@@ -175,6 +182,7 @@
                 DEFAULT_LOCALES);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+        verify(mMockBackupHelper, times(1)).notifyBackupManager();
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -187,6 +195,7 @@
             fail("Expected IllegalArgumentException");
         } finally {
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
         }
     }
 
@@ -217,7 +226,7 @@
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales = mLocaleManagerService.getApplicationLocales(
-                    DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
 
         assertEquals(LocaleList.getEmptyLocaleList(), locales);
     }
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
new file mode 100644
index 0000000..93972c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.locales;
+
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+
+import java.io.File;
+import java.time.Clock;
+
+/**
+ * Shadow for {@link LocaleManagerBackupHelper} to enable mocking it for tests.
+ *
+ * <p>{@link LocaleManagerBackupHelper} is a package private class and hence not mockable directly.
+ */
+public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
+    ShadowLocaleManagerBackupHelper(Context context,
+            LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
+        super(context, localeManagerService, pmInternal, stagedLocalesDir, clock);
+    }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9fec96b..467084a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -837,6 +837,17 @@
             "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ";
 
     /**
+     * Last known cell identity key to be used to fill geo location header in case of an emergency
+     * call. This entry will not be filled if call is not identified as an emergency call.
+     * {@link Connection}. Only provided to the {@link ConnectionService} for the purpose
+     * of placing an emergency call; will not be present in the {@link InCallService} layer.
+     * The {@link ConnectionService}'s implementation will be logged for fine location access
+     * when an outgoing call is placed in this case.
+     */
+    public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY =
+            "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
+
+    /**
      * Boolean connection extra key used to indicate whether device to device communication is
      * available for the current call.
      * @hide
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 2b52af3..1ba997f 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -576,6 +576,17 @@
     }
 
     /**
+     * Check if the caller (or self, if not processing an IPC) has ACCESS_LAST_KNOWN_CELL_ID
+     * permission
+     *
+     * @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
+     */
+    public static boolean checkLastKnownCellIdAccessPermission(Context context) {
+        return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Ensure the caller (or self, if not processing an IPC) has
      * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges.
      *
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6f92c31..7d24b76 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1063,6 +1063,12 @@
             "always_show_emergency_alert_onoff_bool";
 
     /**
+     * Default mobile network MTU value, in bytes.
+     * @hide
+     */
+    public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
+
+    /**
      * The data call retry configuration for different types of APN.
      * @hide
      */
@@ -2914,19 +2920,37 @@
             "signal_strength_nr_nsa_use_lte_as_primary_bool";
 
     /**
+     * String array of TCP buffer sizes per network type.
+     * The entries should be of the following form, with values in bytes:
+     * "network_name:read_min,read_default,read_max,write_min,write_default,write_max".
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
+     * @hide
+     */
+    public static final String KEY_TCP_BUFFERS_STRING_ARRAY = "tcp_buffers_string_array";
+
+    /**
      * String array of default bandwidth values per network type.
-     * The entries should be of form "network_name:downstream,upstream", with values in Kbps.
+     * The entries should be of form: "network_name:downlink,uplink", with values in Kbps.
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
      * @hide
      */
     public static final String KEY_BANDWIDTH_STRING_ARRAY = "bandwidth_string_array";
 
     /**
      * For NR (non-standalone), whether to use the LTE value instead of NR value as the default for
-     * upstream bandwidth. Downstream bandwidth will still use the NR value as the default.
+     * uplink bandwidth. Downlink bandwidth will still use the NR value as the default.
      * @hide
      */
-    public static final String KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL =
-            "bandwidth_nr_nsa_use_lte_value_for_upstream_bool";
+    public static final String KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL =
+            "bandwidth_nr_nsa_use_lte_value_for_uplink_bool";
 
     /**
      * Key identifying if voice call barring notification is required to be shown to the user.
@@ -3628,6 +3652,18 @@
     public static final String KEY_5G_WATCHDOG_TIME_MS_LONG = "5g_watchdog_time_ms_long";
 
     /**
+     * Which NR types are unmetered. A string array containing the following keys:
+     * NR_NSA - NR NSA is unmetered for sub-6 frequencies
+     * NR_NSA_MMWAVE - NR NSA is unmetered for mmwave frequencies
+     * NR_SA - NR SA is unmetered for sub-6 frequencies
+     * NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
+     * TODO: remove other unmetered keys and replace with this
+     * @hide
+     */
+    public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
+            "unmetered_network_types_string_array";
+
+    /**
      * Whether NR (non-standalone) should be unmetered for all frequencies.
      * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
      * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
@@ -5483,6 +5519,7 @@
 
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
+        sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
         sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
                 "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
                         + "320000:5000,640000:5000,1280000:5000,1800000:5000",
@@ -5801,12 +5838,35 @@
                 CellSignalStrengthNr.USE_SSRSRP);
         sDefaults.putBoolean(KEY_SIGNAL_STRENGTH_NR_NSA_USE_LTE_AS_PRIMARY_BOOL, true);
         sDefaults.putStringArray(KEY_BANDWIDTH_STRING_ARRAY, new String[]{
-                "GPRS:24,24", "EDGE:70,18", "UMTS:115,115", "CDMA-IS95A:14,14", "CDMA-IS95B:14,14",
-                "1xRTT:30,30", "EvDo-rev.0:750,48", "EvDo-rev.A:950,550", "HSDPA:4300,620",
-                "HSUPA:4300,1800", "HSPA:4300,1800", "EvDo-rev.B:1500,550", "eHRPD:750,48",
-                "HSPAP:13000,3400", "TD-SCDMA:115,115", "LTE:30000,15000", "NR_NSA:47000,18000",
-                "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000"});
-        sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL, false);
+                "GPRS:24,24", "EDGE:70,18", "UMTS:115,115", "CDMA:14,14",
+                "1xRTT:30,30", "EvDo_0:750,48", "EvDo_A:950,550", "HSDPA:4300,620",
+                "HSUPA:4300,1800", "HSPA:4300,1800", "EvDo_B:1500,550", "eHRPD:750,48",
+                "iDEN:14,14", "LTE:30000,15000", "HSPA+:13000,3400", "GSM:24,24",
+                "TD_SCDMA:115,115", "LTE_CA:30000,15000", "NR_NSA:47000,18000",
+                "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000", "NR_SA_MMWAVE:145000,60000"});
+        sDefaults.putStringArray(KEY_TCP_BUFFERS_STRING_ARRAY, new String[]{
+                "GPRS:4092,8760,48000,4096,8760,48000", "EDGE:4093,26280,70800,4096,16384,70800",
+                "UMTS:58254,349525,1048576,58254,349525,1048576",
+                "CDMA:4094,87380,262144,4096,16384,262144",
+                "1xRTT:16384,32768,131072,4096,16384,102400",
+                "EvDo_0:4094,87380,262144,4096,16384,262144",
+                "EvDo_A:4094,87380,262144,4096,16384,262144",
+                "HSDPA:61167,367002,1101005,8738,52429,262114",
+                "HSUPA:40778,244668,734003,16777,100663,301990",
+                "HSPA:40778,244668,734003,16777,100663,301990",
+                "EvDo_B:4094,87380,262144,4096,16384,262144",
+                "eHRPD:131072,262144,1048576,4096,16384,524288",
+                "iDEN:4094,87380,262144,4096,16384,262144",
+                "LTE:524288,1048576,2097152,262144,524288,1048576",
+                "HSPA+:122334,734003,2202010,32040,192239,576717",
+                "GSM:4092,8760,48000,4096,8760,48000",
+                "TD_SCDMA:58254,349525,1048576,58254,349525,1048576",
+                "LTE_CA:4096,6291456,12582912,4096,1048576,2097152",
+                "NR_NSA:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_NSA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_SA:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_SA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608"});
+        sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL, false);
         sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi");
         sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL, false);
@@ -5832,6 +5892,7 @@
         sDefaults.putInt(KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
         sDefaults.putBoolean(KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, false);
+        sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0267b68..ae22247 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16035,4 +16035,31 @@
             ex.rethrowAsRuntimeException();
         }
     }
+
+    /**
+     * Get last known cell identity.
+     * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
+     * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+     * If there is current registered network this value will be same as the registered cell
+     * identity. If the device goes out of service the previous cell identity is cached and
+     * will be returned. If the cache age of the Cell identity is more than 24 hours
+     * it will be cleared and null will be returned.
+     * @return last known cell identity {@CellIdentity}.
+     * @hide
+     */
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
+            "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
+    public @Nullable CellIdentity getLastKnownCellIdentity() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                throw new IllegalStateException("telephony service is null.");
+            }
+            return telephony.getLastKnownCellIdentity(getSubId(), getOpPackageName(),
+                    getAttributionTag());
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return null;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d5c9ec4..1f336f7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2493,4 +2493,16 @@
      * Unregister an IMS connection state callback
      */
     void unregisterImsStateCallback(in IImsStateCallback cb);
+
+    /**
+     * return last known cell identity
+     * @param subId user preferred subId.
+     * @param callingPackage the name of the package making the call.
+     * @param callingFeatureId The feature in the package.
+     */
+    CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
+            String callingFeatureId);
+
+    /** Check if telephony new data stack is enabled. */
+    boolean isUsingNewDataStack();
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index aec80ac..75f1337 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -141,6 +141,10 @@
  *
  * @param originalLayer Layer that should be visible at the start
  * @param newLayer Layer that should be visible at the end
+ * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored
+ *      when checking the transition. If true we will not fail the assertion if a rotation layer is
+ *      visible to fill the gap between the [originalLayer] being visible and the [newLayer] being
+ *      visible.
  * @param ignoreSnapshot If the snapshot layer should be ignored during the transition
  *     (useful mostly for app launch)
  * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition.
@@ -150,20 +154,23 @@
 fun FlickerTestParameter.replacesLayer(
     originalLayer: FlickerComponentName,
     newLayer: FlickerComponentName,
+    ignoreEntriesWithRotationLayer: Boolean = false,
     ignoreSnapshot: Boolean = false,
     ignoreSplashscreen: Boolean = true
 ) {
     assertLayers {
         val assertion = this.isVisible(originalLayer)
-        if (ignoreSnapshot || ignoreSplashscreen) {
-            assertion.then()
+
+        if (ignoreEntriesWithRotationLayer) {
+            assertion.then().isVisible(FlickerComponentName.ROTATION, isOptional = true)
         }
         if (ignoreSnapshot) {
-            assertion.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+            assertion.then().isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
         }
         if (ignoreSplashscreen) {
-            assertion.isSplashScreenVisibleFor(newLayer, isOptional = true)
+            assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true)
         }
+
         assertion.then().isVisible(newLayer)
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 62e3fa61..b5c81bb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -166,7 +166,8 @@
      * is replaced by [testApp], which remains visible until the end
      */
     open fun appLayerReplacesLauncher() {
-        testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component, ignoreSnapshot = true)
+        testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component,
+                ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true)
     }
 
     /**
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 1ab59a8..05e8bde 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -106,26 +106,26 @@
      * @param files the paths of files which might contain wildcards
      */
     private void deleteFiles(String... files) throws Exception {
-        boolean found = false;
-        for (String file : files) {
-            CommandResult result = getDevice().executeShellV2Command("ls " + file);
-            if (result.getStatus() == CommandStatus.SUCCESS) {
-                found = true;
-                break;
+        try {
+            getDevice().enableAdbRoot();
+            boolean found = false;
+            for (String file : files) {
+                CommandResult result = getDevice().executeShellV2Command("ls " + file);
+                if (result.getStatus() == CommandStatus.SUCCESS) {
+                    found = true;
+                    break;
+                }
             }
-        }
 
-        if (found) {
-            try {
-                getDevice().enableAdbRoot();
+            if (found) {
                 getDevice().remountSystemWritable();
                 for (String file : files) {
                     getDevice().executeShellCommand("rm -rf " + file);
                 }
-            } finally {
-                getDevice().disableAdbRoot();
+                getDevice().reboot();
             }
-            getDevice().reboot();
+        } finally {
+            getDevice().disableAdbRoot();
         }
     }
 
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 926bf1b..f06fa81e 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -112,6 +112,10 @@
      * @param files the paths of files which might contain wildcards
      */
     private void deleteFiles(String... files) throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+
         boolean found = false;
         for (String file : files) {
             CommandResult result = getDevice().executeShellV2Command("ls " + file);
@@ -122,9 +126,6 @@
         }
 
         if (found) {
-            if (!getDevice().isAdbRoot()) {
-                getDevice().enableAdbRoot();
-            }
             getDevice().remountSystemWritable();
             for (String file : files) {
                 getDevice().executeShellCommand("rm -rf " + file);
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
index f7d36970..476be44 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
@@ -36,7 +36,7 @@
         return new VcnCellUnderlyingNetworkPriority.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
                 .setAllowMetered(true /* allowMetered */)
-                .setAllowedPlmnIds(ALLOWED_PLMN_IDS)
+                .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS)
                 .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
                 .setAllowRoaming(true /* allowRoaming */)
                 .setRequireOpportunistic(true /* requireOpportunistic */)
@@ -48,7 +48,7 @@
         final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
         assertTrue(networkPriority.allowMetered());
-        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedPlmnIds());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds());
         assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
         assertTrue(networkPriority.allowRoaming());
         assertTrue(networkPriority.requireOpportunistic());
@@ -60,7 +60,7 @@
                 new VcnCellUnderlyingNetworkPriority.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
         assertFalse(networkPriority.allowMetered());
-        assertEquals(new HashSet<String>(), networkPriority.getAllowedPlmnIds());
+        assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds());
         assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
         assertFalse(networkPriority.allowRoaming());
         assertFalse(networkPriority.requireOpportunistic());
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 724c33f..377f526 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -17,6 +17,8 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES;
+import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -30,6 +32,7 @@
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
 import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest;
 import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest;
+import android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -230,6 +233,16 @@
         assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
     }
 
+    @Test
+    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() {
+        PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
+        configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null);
+
+        final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle);
+        assertEquals(
+                DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+    }
+
     private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) {
         final IkeSessionParams ikeParams =
                 IkeSessionParamsUtilsTest.createBuilderMinimum()
@@ -271,4 +284,40 @@
         assertNotEquals(tunnelParams, anotherTunnelParams);
         assertNotEquals(config, anotherConfig);
     }
+
+    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities(
+            LinkedHashSet<VcnUnderlyingNetworkPriority> networkPriorities) {
+        return buildTestConfigWithExposedCaps(
+                new VcnGatewayConnectionConfig.Builder(
+                                "buildTestConfigWithVcnUnderlyingNetworkPriorities",
+                                TUNNEL_CONNECTION_PARAMS)
+                        .setVcnUnderlyingNetworkPriorities(networkPriorities),
+                EXPOSED_CAPS);
+    }
+
+    @Test
+    public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+
+        final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesEqual =
+                new LinkedHashSet();
+        networkPrioritiesEqual.add(VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        final VcnGatewayConnectionConfig configEqual =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual);
+
+        final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesNotEqual =
+                new LinkedHashSet();
+        networkPrioritiesNotEqual.add(
+                VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        final VcnGatewayConnectionConfig configNotEqual =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual);
+
+        assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual);
+        assertEquals(config, configEqual);
+
+        assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual);
+        assertNotEquals(config, configNotEqual);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 2e1aab6..46a614f 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -279,7 +279,7 @@
         final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
         final VcnCellUnderlyingNetworkPriority networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedPlmnIds(Set.of(networkPriorityPlmnId))
+                        .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId))
                         .build();
 
         assertEquals(