Merge "TARE Factors: Keys and Default Values"
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 74ed334..829ad27 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -26,6 +26,8 @@
 import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
 import static com.android.server.tare.EconomicPolicy.eventToString;
 import static com.android.server.tare.EconomicPolicy.getEventType;
+import static com.android.server.tare.TareUtils.appToString;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
 import static com.android.server.tare.TareUtils.narcToString;
 
 import android.annotation.NonNull;
@@ -183,7 +185,7 @@
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents != null) {
             final long nowElapsed = SystemClock.elapsedRealtime();
-            final long now = System.currentTimeMillis();
+            final long now = getCurrentTimeMillis();
             mTotalDeltaCalculator.reset(ledger, nowElapsed, now);
             ongoingEvents.forEach(mTotalDeltaCalculator);
             balance += mTotalDeltaCalculator.mTotal;
@@ -194,7 +196,12 @@
     @GuardedBy("mLock")
     void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
             final int eventId, @Nullable String tag) {
-        final long now = System.currentTimeMillis();
+        if (mIrs.isSystem(userId, pkgName)) {
+            // Events are free for the system. Don't bother recording them.
+            return;
+        }
+
+        final long now = getCurrentTimeMillis();
         final Ledger ledger = getLedgerLocked(userId, pkgName);
 
         final int eventType = getEventType(eventId);
@@ -278,7 +285,7 @@
 
     @GuardedBy("mLock")
     void onDeviceStateChangedLocked() {
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
 
         mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
@@ -326,7 +333,7 @@
 
     @GuardedBy("mLock")
     void onAppStatesChangedLocked(final int userId, @NonNull ArraySet<String> pkgNames) {
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
 
         for (int i = 0; i < pkgNames.size(); ++i) {
@@ -415,12 +422,15 @@
         }
         ongoingEvent.refCount--;
         if (ongoingEvent.refCount <= 0) {
-            final long startElapsed = ongoingEvent.startTimeElapsed;
-            final long startTime = now - (nowElapsed - startElapsed);
-            final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
-            recordTransactionLocked(userId, pkgName, ledger,
-                    new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
-                    notifyOnAffordabilityChange);
+            if (!mIrs.isSystem(userId, pkgName)) {
+                final long startElapsed = ongoingEvent.startTimeElapsed;
+                final long startTime = now - (nowElapsed - startElapsed);
+                final long actualDelta =
+                        getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
+                recordTransactionLocked(userId, pkgName, ledger,
+                        new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
+                        notifyOnAffordabilityChange);
+            }
             ongoingEvents.delete(eventId, tag);
         }
         if (updateBalanceCheck) {
@@ -446,6 +456,15 @@
     private void recordTransactionLocked(final int userId, @NonNull final String pkgName,
             @NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
             final boolean notifyOnAffordabilityChange) {
+        if (transaction.delta == 0) {
+            // Skip recording transactions with a delta of 0 to save on space.
+            return;
+        }
+        if (mIrs.isSystem(userId, pkgName)) {
+            Slog.wtfStack(TAG,
+                    "Tried to adjust system balance for " + appToString(userId, pkgName));
+            return;
+        }
         final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
         final long newArcsInCirculation = mCurrentNarcsInCirculation + transaction.delta;
         if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
@@ -477,7 +496,7 @@
             // The earliest transaction won't change until we clean up the ledger, so no point
             // continuing to reschedule an existing cleanup.
             final long cleanupAlarmElapsed = SystemClock.elapsedRealtime() + MAX_TRANSACTION_AGE_MS
-                    - (System.currentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
+                    - (getCurrentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
             mLedgerCleanupAlarmListener.addAlarmLocked(userId, pkgName, cleanupAlarmElapsed);
         }
         // TODO: save changes to disk in a background thread
@@ -509,7 +528,7 @@
     @GuardedBy("mLock")
     void reclaimUnusedAssetsLocked(double percentage) {
         final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
         for (int i = 0; i < pkgs.size(); ++i) {
             final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
             final String pkgName = pkgs.get(i).packageName;
@@ -543,11 +562,15 @@
     @GuardedBy("mLock")
     void distributeBasicIncomeLocked(int batteryLevel) {
         List<PackageInfo> pkgs = mIrs.getInstalledPackages();
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
         for (int i = 0; i < pkgs.size(); ++i) {
             final PackageInfo pkgInfo = pkgs.get(i);
             final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
             final String pkgName = pkgInfo.packageName;
+            if (mIrs.isSystem(userId, pkgName)) {
+                // No point allocating ARCs to the system. It can do whatever it wants.
+                continue;
+            }
             Ledger ledger = getLedgerLocked(userId, pkgName);
             final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
             final double perc = batteryLevel / 100d;
@@ -578,12 +601,16 @@
         List<PackageInfo> pkgs = packageManager.getInstalledPackagesAsUser(0, userId);
         final long maxBirthright =
                 mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
 
         for (int i = 0; i < pkgs.size(); ++i) {
             final PackageInfo packageInfo = pkgs.get(i);
             final String pkgName = packageInfo.packageName;
             final Ledger ledger = getLedgerLocked(userId, pkgName);
+            if (mIrs.isSystem(userId, pkgName)) {
+                // No point allocating ARCs to the system. It can do whatever it wants.
+                continue;
+            }
             if (ledger.getCurrentBalance() > 0) {
                 // App already got credits somehow. Move along.
                 Slog.wtf(TAG, "App " + pkgName + " had credits before economy was set up");
@@ -609,7 +636,7 @@
         List<PackageInfo> pkgs = mIrs.getInstalledPackages();
         final int numPackages = pkgs.size();
         final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
 
         recordTransactionLocked(userId, pkgName, ledger,
                 new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
index 831c05f..29aa946 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
@@ -124,6 +124,11 @@
      */
     boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill);
 
+    /**
+     * Returns the maximum duration (in milliseconds) that the specified app can afford the bill,
+     * based on current prices.
+     */
+    long getMaxDurationMs(int userId, @NonNull String pkgName, @NonNull ActionBill bill);
 
     /**
      * Register an {@link AffordabilityChangeListener} to track when an app's ability to afford the
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 67d7a95..a2ba75c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -19,6 +19,8 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
@@ -30,6 +32,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManagerInternal;
@@ -45,6 +48,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 import android.util.SparseSetArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,6 +85,7 @@
     private final Handler mHandler;
     private final BatteryManagerInternal mBatteryManagerInternal;
     private final PackageManager mPackageManager;
+    private final PackageManagerInternal mPackageManagerInternal;
 
     private final Agent mAgent;
     private final CompleteEconomicPolicy mCompleteEconomicPolicy;
@@ -95,6 +100,10 @@
     @GuardedBy("mLock")
     private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
 
+    /** Cached mapping of userId+package to their UIDs (for all users) */
+    @GuardedBy("mPackageToUidCache")
+    private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
+
     private volatile boolean mIsEnabled;
     private volatile int mBootPhase;
     // In the range [0,100] to represent 0% to 100% battery.
@@ -104,7 +113,6 @@
     @GuardedBy("mLock")
     private long mLastUnusedReclamationTime;
 
-    @SuppressWarnings("FieldCanBeLocal")
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Nullable
         private String getPackageName(Intent intent) {
@@ -159,7 +167,7 @@
                 public void onAlarm() {
                     synchronized (mLock) {
                         mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
-                        mLastUnusedReclamationTime = System.currentTimeMillis();
+                        mLastUnusedReclamationTime = getCurrentTimeMillis();
                         scheduleUnusedWealthReclamationLocked();
                     }
                 }
@@ -185,6 +193,7 @@
         mHandler = new IrsHandler(TareHandlerThread.get().getLooper());
         mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
         mPackageManager = context.getPackageManager();
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mEconomyManagerStub = new EconomyManagerStub();
         mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
         mAgent = new Agent(this, mCompleteEconomicPolicy);
@@ -232,10 +241,28 @@
                 / 100;
     }
 
+    int getUid(final int userId, @NonNull final String pkgName) {
+        synchronized (mPackageToUidCache) {
+            Integer uid = mPackageToUidCache.get(userId, pkgName);
+            if (uid == null) {
+                uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+                mPackageToUidCache.add(userId, pkgName, uid);
+            }
+            return uid;
+        }
+    }
+
     boolean isEnabled() {
         return mIsEnabled;
     }
 
+    boolean isSystem(final int userId, @NonNull String pkgName) {
+        if ("android".equals(pkgName)) {
+            return true;
+        }
+        return UserHandle.isCore(getUid(userId, pkgName));
+    }
+
     void onBatteryLevelChanged() {
         synchronized (mLock) {
             final int newBatteryLevel = getCurrentBatteryLevel();
@@ -261,6 +288,9 @@
             Slog.wtf(TAG, "PM couldn't find newly added package: " + pkgName);
             return;
         }
+        synchronized (mPackageToUidCache) {
+            mPackageToUidCache.add(userId, pkgName, uid);
+        }
         synchronized (mLock) {
             mPkgCache.add(packageInfo);
             mUidToPackageCache.add(uid, pkgName);
@@ -277,6 +307,9 @@
 
     void onPackageRemoved(final int uid, @NonNull final String pkgName) {
         final int userId = UserHandle.getUserId(uid);
+        synchronized (mPackageToUidCache) {
+            mPackageToUidCache.delete(userId, pkgName);
+        }
         synchronized (mLock) {
             mUidToPackageCache.remove(uid, pkgName);
             for (int i = 0; i < mPkgCache.size(); ++i) {
@@ -342,7 +375,7 @@
 
     @GuardedBy("mLock")
     private void scheduleUnusedWealthReclamationLocked() {
-        final long now = System.currentTimeMillis();
+        final long now = getCurrentTimeMillis();
         final long nextReclamationTime =
                 Math.max(mLastUnusedReclamationTime + UNUSED_RECLAMATION_PERIOD_MS, now + 30_000);
         mHandler.post(() -> {
@@ -447,6 +480,9 @@
             mUidToPackageCache.clear();
             getContext().unregisterReceiver(mBroadcastReceiver);
         }
+        synchronized (mPackageToUidCache) {
+            mPackageToUidCache.clear();
+        }
     }
 
     private final class IrsHandler extends Handler {
@@ -514,9 +550,22 @@
     }
 
     private final class LocalService implements EconomyManagerInternal {
+        /**
+         * Use an extremely large value to indicate that an app can pay for a bill indefinitely.
+         * The value set here should be large/long enough that there's no reasonable expectation
+         * of a device operating uninterrupted (or in the exact same state) for that period of time.
+         * We intentionally don't use Long.MAX_VALUE to avoid potential overflow if a client
+         * doesn't check the value and just immediately adds it to the current time.
+         */
+        private static final long FOREVER_MS = 27 * 365 * 24 * HOUR_IN_MILLIS;
+
         @Override
         public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName,
                 @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
+            if (isSystem(userId, pkgName)) {
+                // The system's affordability never changes.
+                return;
+            }
             synchronized (mLock) {
                 mAgent.registerAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
             }
@@ -525,6 +574,10 @@
         @Override
         public void unregisterAffordabilityChangeListener(int userId, @NonNull String pkgName,
                 @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
+            if (isSystem(userId, pkgName)) {
+                // The system's affordability never changes.
+                return;
+            }
             synchronized (mLock) {
                 mAgent.unregisterAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
             }
@@ -535,6 +588,11 @@
             if (!mIsEnabled) {
                 return true;
             }
+            if (isSystem(userId, pkgName)) {
+                // The government, I mean the system, can create ARCs as it needs to in order to
+                // operate.
+                return true;
+            }
             // TODO: take temp-allowlist into consideration
             long requiredBalance = 0;
             final List<EconomyManagerInternal.AnticipatedAction> projectedActions =
@@ -552,6 +610,32 @@
         }
 
         @Override
+        public long getMaxDurationMs(int userId, @NonNull String pkgName,
+                @NonNull ActionBill bill) {
+            if (!mIsEnabled) {
+                return FOREVER_MS;
+            }
+            if (isSystem(userId, pkgName)) {
+                return FOREVER_MS;
+            }
+            long totalCostPerSecond = 0;
+            final List<EconomyManagerInternal.AnticipatedAction> projectedActions =
+                    bill.getAnticipatedActions();
+            for (int i = 0; i < projectedActions.size(); ++i) {
+                AnticipatedAction action = projectedActions.get(i);
+                final long cost =
+                        mCompleteEconomicPolicy.getCostOfAction(action.actionId, userId, pkgName);
+                totalCostPerSecond += cost;
+            }
+            if (totalCostPerSecond == 0) {
+                return FOREVER_MS;
+            }
+            synchronized (mLock) {
+                return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
+            }
+        }
+
+        @Override
         public void noteInstantaneousEvent(int userId, @NonNull String pkgName, int eventId,
                 @Nullable String tag) {
             if (!mIsEnabled) {
@@ -581,7 +665,7 @@
                 return;
             }
             final long nowElapsed = SystemClock.elapsedRealtime();
-            final long now = System.currentTimeMillis();
+            final long now = getCurrentTimeMillis();
             synchronized (mLock) {
                 mAgent.stopOngoingActionLocked(userId, pkgName, eventId, tag, nowElapsed, now);
             }
@@ -630,6 +714,9 @@
 
     private void dumpInternal(final IndentingPrintWriter pw) {
         synchronized (mLock) {
+            pw.print("Is enabled: ");
+            pw.println(mIsEnabled);
+
             pw.print("Current battery level: ");
             pw.println(mCurrentBatteryLevel);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index ae1bf26..76543d7 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -19,6 +19,7 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.server.tare.TareUtils.dumpTime;
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
 import static com.android.server.tare.TareUtils.narcToString;
 
 import android.annotation.NonNull;
@@ -109,8 +110,8 @@
 
     /** Deletes transactions that are older than {@code minAgeMs}. */
     void removeOldTransactions(long minAgeMs) {
-        final long cutoff = System.currentTimeMillis() - minAgeMs;
-        while (mTransactions.get(0).endTimeMs <= cutoff) {
+        final long cutoff = getCurrentTimeMillis() - minAgeMs;
+        while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) {
             mTransactions.remove(0);
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
index 92a2014a..67a3dc6 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
-import android.content.pm.PackageManagerInternal;
 import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
@@ -28,7 +27,6 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -56,7 +54,6 @@
 
     private final Object mLock = new Object();
     private final InternalResourceService mIrs;
-    private final PackageManagerInternal mPackageManagerInternal;
 
     /** Cached mapping of userId+package to their UIDs (for all users) */
     private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
@@ -105,7 +102,6 @@
     ProcessStateModifier(@NonNull InternalResourceService irs) {
         super();
         mIrs = irs;
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
     }
 
     @Override
@@ -143,7 +139,7 @@
         final int procState;
         synchronized (mLock) {
             procState = mUidProcStateBucketCache.get(
-                    getUidLocked(userId, pkgName), PROC_STATE_BUCKET_NONE);
+                    mIrs.getUid(userId, pkgName), PROC_STATE_BUCKET_NONE);
         }
         switch (procState) {
             case PROC_STATE_BUCKET_TOP:
@@ -166,15 +162,6 @@
         pw.println(mUidProcStateBucketCache);
     }
 
-    @GuardedBy("mLock")
-    private int getUidLocked(final int userId, @NonNull final String pkgName) {
-        if (!mPackageToUidCache.contains(userId, pkgName)) {
-            mPackageToUidCache.add(userId, pkgName,
-                    mPackageManagerInternal.getPackageUid(pkgName, 0, userId));
-        }
-        return mPackageToUidCache.get(userId, pkgName);
-    }
-
     @ProcStateBucket
     private int getProcStateBucket(int procState) {
         if (procState <= ActivityManager.PROCESS_STATE_TOP) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
index 2d72f56..1e047aa 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
@@ -20,7 +20,10 @@
 import android.annotation.SuppressLint;
 import android.util.IndentingPrintWriter;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.text.SimpleDateFormat;
+import java.time.Clock;
 
 class TareUtils {
     private static final long NARC_IN_ARC = 1_000_000_000L;
@@ -29,6 +32,9 @@
     private static final SimpleDateFormat sDumpDateFormat =
             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
+    @VisibleForTesting
+    static Clock sSystemClock = Clock.systemUTC();
+
     static long arcToNarc(int arcs) {
         return arcs * NARC_IN_ARC;
     }
@@ -37,6 +43,10 @@
         pw.print(sDumpDateFormat.format(time));
     }
 
+    static long getCurrentTimeMillis() {
+        return sSystemClock.millis();
+    }
+
     static int narcToArc(long narcs) {
         return (int) (narcs / NARC_IN_ARC);
     }
@@ -59,4 +69,10 @@
         sb.append(" ARCs");
         return sb.toString();
     }
+
+    /** Returns a standardized format for printing userId+pkgName combinations. */
+    @NonNull
+    static String appToString(int userId, String pkgName) {
+        return "<" + userId + ">" + pkgName;
+    }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index c7791d9..396c080 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31684,6 +31684,7 @@
     method public boolean isDeviceIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
+    method public boolean isLightDeviceIdleMode();
     method public boolean isPowerSaveMode();
     method public boolean isRebootingUserspaceSupported();
     method @Deprecated public boolean isScreenOn();
@@ -31694,6 +31695,7 @@
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
+    field public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -34832,6 +34834,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+    field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
     field public static final android.net.Uri CONTENT_URI;
@@ -51941,7 +51944,7 @@
     method public boolean finishComposingText();
     method public int getCursorCapsMode(int);
     method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
-    method public android.os.Handler getHandler();
+    method @Nullable public android.os.Handler getHandler();
     method public CharSequence getSelectedText(int);
     method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 45db0f6..fd6589c 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2112,7 +2112,8 @@
                     throw new ActivityNotFoundException(
                             "Unable to find explicit activity class "
                             + ((Intent)intent).getComponent().toShortString()
-                            + "; have you declared this activity in your AndroidManifest.xml?");
+                            + "; have you declared this activity in your AndroidManifest.xml"
+                            + ", or does your intent not match its declared <intent-filter>?");
                 throw new ActivityNotFoundException(
                         "No Activity found to handle " + intent);
             case ActivityManager.START_PERMISSION_DENIED:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 27db25d..9a37d04 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -13736,8 +13736,6 @@
      *
      * It is recommended that the Enterprise ID is at least 6 characters long, and no more than
      * 64 characters.
-     * This API is supposed to be called only after the boot phase is complete,
-     * throws {@link IllegalStateException} if called before boot phase is complete.
      *
      * @param enterpriseId An identifier of the organization this work profile or device is
      *                     enrolled into.
diff --git a/core/java/android/appwidget/AppWidgetManagerInternal.java b/core/java/android/appwidget/AppWidgetManagerInternal.java
index 266e33a..5694ca8 100644
--- a/core/java/android/appwidget/AppWidgetManagerInternal.java
+++ b/core/java/android/appwidget/AppWidgetManagerInternal.java
@@ -19,8 +19,6 @@
 import android.annotation.Nullable;
 import android.util.ArraySet;
 
-import java.util.Set;
-
 /**
  * App widget manager local system service interface.
  *
@@ -44,16 +42,4 @@
      * @param userId The user that is being unlocked.
      */
     public abstract void unlockUser(int userId);
-
-    /**
-     * Updates all widgets, applying changes to Runtime Resource Overlay affecting the specified
-     * target packages.
-     *
-     * @param packageNames The names of all target packages for which an overlay was modified
-     * @param userId The user for which overlay modifications occurred.
-     * @param updateFrameworkRes Whether or not an overlay affected the values of framework
-     *                           resources.
-     */
-    public abstract void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
-            boolean updateFrameworkRes);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bed0383..29ce397 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -55,6 +55,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.split.SplitAssetLoader;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.ApkAssets;
@@ -7425,7 +7427,7 @@
             mCompileSdkVersionCodename = dest.readString();
             mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
 
-            mKeySetMapping = readKeySetMapping(dest);
+            mKeySetMapping = ParsingPackageUtils.readKeySetMapping(dest);
 
             cpuAbiOverride = dest.readString();
             use32bitAbi = (dest.readInt() == 1);
@@ -7551,73 +7553,13 @@
             dest.writeInt(mCompileSdkVersion);
             dest.writeString(mCompileSdkVersionCodename);
             dest.writeArraySet(mUpgradeKeySets);
-            writeKeySetMapping(dest, mKeySetMapping);
+            ParsingPackageUtils.writeKeySetMapping(dest, mKeySetMapping);
             dest.writeString(cpuAbiOverride);
             dest.writeInt(use32bitAbi ? 1 : 0);
             dest.writeByteArray(restrictUpdateHash);
             dest.writeInt(visibleToInstantApps ? 1 : 0);
         }
 
-        /**
-         * Writes the keyset mapping to the provided package. {@code null} mappings are permitted.
-         */
-        private static void writeKeySetMapping(
-                Parcel dest, ArrayMap<String, ArraySet<PublicKey>> keySetMapping) {
-            if (keySetMapping == null) {
-                dest.writeInt(-1);
-                return;
-            }
-
-            final int N = keySetMapping.size();
-            dest.writeInt(N);
-
-            for (int i = 0; i < N; i++) {
-                dest.writeString(keySetMapping.keyAt(i));
-                ArraySet<PublicKey> keys = keySetMapping.valueAt(i);
-                if (keys == null) {
-                    dest.writeInt(-1);
-                    continue;
-                }
-
-                final int M = keys.size();
-                dest.writeInt(M);
-                for (int j = 0; j < M; j++) {
-                    dest.writeSerializable(keys.valueAt(j));
-                }
-            }
-        }
-
-        /**
-         * Reads a keyset mapping from the given parcel at the given data position. May return
-         * {@code null} if the serialized mapping was {@code null}.
-         */
-        private static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(Parcel in) {
-            final int N = in.readInt();
-            if (N == -1) {
-                return null;
-            }
-
-            ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>();
-            for (int i = 0; i < N; ++i) {
-                String key = in.readString();
-                final int M = in.readInt();
-                if (M == -1) {
-                    keySetMapping.put(key, null);
-                    continue;
-                }
-
-                ArraySet<PublicKey> keys = new ArraySet<>(M);
-                for (int j = 0; j < M; ++j) {
-                    PublicKey pk = (PublicKey) in.readSerializable();
-                    keys.add(pk);
-                }
-
-                keySetMapping.put(key, keys);
-            }
-
-            return keySetMapping;
-        }
-
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Package>() {
             public Package createFromParcel(Parcel in) {
                 return new Package(in);
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index 9fee7bb..f2a6a5c 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -535,7 +535,7 @@
         ai.setMaxAspectRatio(maxAspectRatio != null ? maxAspectRatio : 0f);
         Float minAspectRatio = a.getMinAspectRatio();
         ai.setMinAspectRatio(minAspectRatio != null ? minAspectRatio : 0f);
-        ai.supportsSizeChanges = a.getSupportsSizeChanges();
+        ai.supportsSizeChanges = a.isSupportsSizeChanges();
         ai.requestedVrComponent = a.getRequestedVrComponent();
         ai.rotationAnimation = a.getRotationAnimation();
         ai.colorMode = a.getColorMode();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 72cc929..d6e1ac9 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -360,7 +360,7 @@
 
     ParsingPackage setCompileSdkVersion(int compileSdkVersion);
 
-    ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename);
+    ParsingPackage setCompileSdkVersionCodeName(String compileSdkVersionCodeName);
 
     ParsingPackage setAttributionsAreUserVisible(boolean attributionsAreUserVisible);
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 0db6546..f0d95d9 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -553,7 +553,7 @@
 
             setCompileSdkVersion(manifestArray.getInteger(
                     R.styleable.AndroidManifest_compileSdkVersion, 0));
-            setCompileSdkVersionCodename(manifestArray.getNonConfigurationString(
+            setCompileSdkVersionCodeName(manifestArray.getNonConfigurationString(
                     R.styleable.AndroidManifest_compileSdkVersionCodename, 0));
 
             setIsolatedSplitLoading(manifestArray.getBoolean(
@@ -2686,8 +2686,8 @@
     }
 
     @Override
-    public ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename) {
-        this.compileSdkVersionCodeName = compileSdkVersionCodename;
+    public ParsingPackage setCompileSdkVersionCodeName(String compileSdkVersionCodeName) {
+        this.compileSdkVersionCodeName = compileSdkVersionCodeName;
         return this;
     }
 
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 73ee132..adb6b76 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -23,6 +23,7 @@
 import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
@@ -423,6 +424,7 @@
         }
     }
 
+    @NonNull
     public static final Parcelable.Creator<ParsedActivity> CREATOR = new Creator<ParsedActivity>() {
         @Override
         public ParsedActivity createFromParcel(Parcel source) {
@@ -513,10 +515,6 @@
         return minAspectRatio;
     }
 
-    public boolean getSupportsSizeChanges() {
-        return supportsSizeChanges;
-    }
-
     @Nullable
     public String getRequestedVrComponent() {
         return requestedVrComponent;
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/core/java/android/content/pm/parsing/component/ParsedComponent.java
index 3c0f097..838adfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedComponent.java
+++ b/core/java/android/content/pm/parsing/component/ParsedComponent.java
@@ -43,8 +43,8 @@
 /** @hide */
 public abstract class ParsedComponent implements Parcelable {
 
-    private static ParsedIntentInfo.ListParceler sForIntentInfos = Parcelling.Cache.getOrCreate(
-            ParsedIntentInfo.ListParceler.class);
+    private static final ParsedIntentInfo.ListParceler sForIntentInfos =
+            Parcelling.Cache.getOrCreate(ParsedIntentInfo.ListParceler.class);
 
     @NonNull
     @DataClass.ParcelWith(ForInternedString.class)
diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
index 65ff472..4178920 100644
--- a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
+++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.Parcel;
@@ -94,6 +95,7 @@
         this.functionalTest = in.readByte() != 0;
     }
 
+    @NonNull
     public static final Parcelable.Creator<ParsedInstrumentation> CREATOR =
             new Parcelable.Creator<ParsedInstrumentation>() {
                 @Override
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
index 01ee0f4..59d4a95 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.parsing.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
 import android.os.Parcel;
@@ -58,6 +59,7 @@
             item.writeIntentInfoToParcel(dest, parcelFlags);
         }
 
+        @NonNull
         @Override
         public ParsedIntentInfo unparcel(Parcel source) {
             return new ParsedIntentInfo(source);
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index 50bc3d9..0f82941 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.parsing.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PermissionInfo;
 import android.os.Parcel;
@@ -167,6 +168,7 @@
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
+    @NonNull
     public static final Parcelable.Creator<ParsedPermission> CREATOR =
             new Parcelable.Creator<ParsedPermission>() {
                 @Override
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index c39d6b1..fe10225 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -19,7 +19,6 @@
 import static java.util.Collections.emptySet;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/core/java/android/content/pm/parsing/component/ParsedProvider.java
index ebf85f7..9a12b48 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProvider.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProvider.java
@@ -169,6 +169,7 @@
         this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
     }
 
+    @NonNull
     public static final Parcelable.Creator<ParsedProvider> CREATOR = new Creator<ParsedProvider>() {
         @Override
         public ParsedProvider createFromParcel(Parcel source) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/core/java/android/content/pm/parsing/component/ParsedService.java
index 471d346..5499a13 100644
--- a/core/java/android/content/pm/parsing/component/ParsedService.java
+++ b/core/java/android/content/pm/parsing/component/ParsedService.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.Parcel;
@@ -83,6 +84,7 @@
         this.permission = sForInternedString.unparcel(in);
     }
 
+    @NonNull
     public static final Parcelable.Creator<ParsedService> CREATOR = new Creator<ParsedService>() {
         @Override
         public ParsedService createFromParcel(Parcel source) {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index d5a35bc..02245e4 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -446,16 +446,12 @@
         }
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-        }
-        super.finalize();
-    }
+    public void release(boolean skipCloseNotification) {
+        boolean notifyClose = false;
 
-    public void release() {
         synchronized (mInterfaceLock) {
+            mHandlerThread.quitSafely();
+
             if (mSessionProcessor != null) {
                 try {
                     mSessionProcessor.deInitSession();
@@ -469,6 +465,7 @@
             if (mExtensionClientId >= 0) {
                 CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
                 if (mInitialized) {
+                    notifyClose = true;
                     CameraExtensionCharacteristics.releaseSession();
                 }
             }
@@ -482,6 +479,16 @@
             mClientRepeatingRequestSurface = null;
             mClientCaptureSurface = null;
         }
+
+        if (notifyClose && !skipCloseNotification) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallbacks.onClosed(
+                        CameraAdvancedExtensionSessionImpl.this));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 
     private void notifyConfigurationFailure() {
@@ -491,7 +498,7 @@
             }
         }
 
-        release();
+        release(true /*skipCloseNotification*/);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -507,15 +514,7 @@
             android.hardware.camera2.CameraCaptureSession.StateCallback {
         @Override
         public void onClosed(@NonNull CameraCaptureSession session) {
-            release();
-
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mCallbacks.onClosed(
-                       CameraAdvancedExtensionSessionImpl.this));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+            release(false /*skipCloseNotification*/);
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 0bf812e..fc728a2 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -697,12 +697,12 @@
             }
 
             if (mCurrentExtensionSession != null) {
-                mCurrentExtensionSession.release();
+                mCurrentExtensionSession.release(false /*skipCloseNotification*/);
                 mCurrentExtensionSession = null;
             }
 
             if (mCurrentAdvancedExtensionSession != null) {
-                mCurrentAdvancedExtensionSession.release();
+                mCurrentAdvancedExtensionSession.release(false /*skipCloseNotification*/);
                 mCurrentAdvancedExtensionSession = null;
             }
 
@@ -1352,12 +1352,12 @@
             }
 
             if (mCurrentExtensionSession != null) {
-                mCurrentExtensionSession.release();
+                mCurrentExtensionSession.release(true /*skipCloseNotification*/);
                 mCurrentExtensionSession = null;
             }
 
             if (mCurrentAdvancedExtensionSession != null) {
-                mCurrentAdvancedExtensionSession.release();
+                mCurrentAdvancedExtensionSession.release(true /*skipCloseNotification*/);
                 mCurrentAdvancedExtensionSession = null;
             }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 7d29a7d..ecd2491 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -630,18 +630,13 @@
                 new CameraExtensionUtils.HandlerExecutor(mHandler), requestHandler);
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-        }
-        super.finalize();
-    }
-
     /** @hide */
-    public void release() {
+    public void release(boolean skipCloseNotification) {
+        boolean notifyClose = false;
+
         synchronized (mInterfaceLock) {
             mInternalRepeatingRequestEnabled = false;
+            mHandlerThread.quitSafely();
 
             try {
                 mPreviewExtender.onDeInit();
@@ -654,6 +649,7 @@
             if (mExtensionClientId >= 0) {
                 CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
                 if (mInitialized) {
+                    notifyClose = true;
                     CameraExtensionCharacteristics.releaseSession();
                 }
             }
@@ -704,6 +700,15 @@
             mCameraRepeatingSurface = mClientRepeatingRequestSurface = null;
             mCameraBurstSurface = mClientCaptureSurface = null;
         }
+
+        if (notifyClose && !skipCloseNotification) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallbacks.onClosed(CameraExtensionSessionImpl.this));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 
     private void notifyConfigurationFailure() {
@@ -713,7 +718,7 @@
             }
         }
 
-        release();
+        release(true /*skipCloseNotification*/);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -745,14 +750,7 @@
             android.hardware.camera2.CameraCaptureSession.StateCallback {
         @Override
         public void onClosed(@NonNull CameraCaptureSession session) {
-            release();
-
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mCallbacks.onClosed(CameraExtensionSessionImpl.this));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+            release(false /*skipCloseNotification*/);
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 8fd9a6a..196134b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1720,21 +1720,21 @@
                 new SetCommand() {
             @Override
             public <T> void setValue(CameraMetadataNative metadata, T value) {
-                metadata.setAWBRegions((MeteringRectangle[]) value);
+                metadata.setAWBRegions(value);
             }
         });
         sSetCommandMap.put(CaptureRequest.CONTROL_AF_REGIONS.getNativeKey(),
                 new SetCommand() {
             @Override
             public <T> void setValue(CameraMetadataNative metadata, T value) {
-                metadata.setAFRegions((MeteringRectangle[]) value);
+                metadata.setAFRegions(value);
             }
         });
         sSetCommandMap.put(CaptureRequest.CONTROL_AE_REGIONS.getNativeKey(),
                 new SetCommand() {
             @Override
             public <T> void setValue(CameraMetadataNative metadata, T value) {
-                metadata.setAERegions((MeteringRectangle[]) value);
+                metadata.setAERegions(value);
             }
         });
     }
@@ -1815,30 +1815,33 @@
         return true;
     }
 
-    private <T> boolean setAFRegions(MeteringRectangle[] afRegions) {
+    private <T> boolean setAFRegions(T afRegions) {
         if (afRegions == null) {
             return false;
         }
         setBase(CaptureRequest.CONTROL_AF_REGIONS_SET, true);
-        setBase(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
+        // The cast to CaptureRequest.Key is needed since java does not support template
+        // specialization and we need to route this method to
+        // setBase(CaptureRequest.Key<T> key, T value)
+        setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AF_REGIONS, afRegions);
         return true;
     }
 
-    private <T> boolean setAERegions(MeteringRectangle[] aeRegions) {
+    private <T> boolean setAERegions(T aeRegions) {
         if (aeRegions == null) {
             return false;
         }
         setBase(CaptureRequest.CONTROL_AE_REGIONS_SET, true);
-        setBase(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+        setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
         return true;
     }
 
-    private <T> boolean setAWBRegions(MeteringRectangle[] awbRegions) {
+    private <T> boolean setAWBRegions(T awbRegions) {
         if (awbRegions == null) {
             return false;
         }
         setBase(CaptureRequest.CONTROL_AWB_REGIONS_SET, true);
-        setBase(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+        setBase((CaptureRequest.Key)CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
         return true;
     }
 
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index f38271a..403f55c 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.os.IDropBoxManagerService;
 
+import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.File;
@@ -257,7 +258,8 @@
             } else {
                 return null;
             }
-            return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+            return (mFlags & IS_GZIPPED) != 0
+                ? new GZIPInputStream(new BufferedInputStream(is)) : is;
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3aa0bcb..2f66a39 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2092,9 +2092,7 @@
      * when light idle mode restrictions are being actively applied; it will return false if the
      * device is in a long-term idle mode but currently running a maintenance window where
      * restrictions have been lifted.
-     * @hide
      */
-    @UnsupportedAppUsage
     public boolean isLightDeviceIdleMode() {
         try {
             return mService.isLightDeviceIdleMode();
@@ -2555,9 +2553,7 @@
     /**
      * Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
      * This broadcast is only sent to registered receivers.
-     * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
             = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b3d60a5..570d73d 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8588,6 +8588,15 @@
          * Type: INTEGER
          */
         public static final String UNGROUPED_WITH_PHONES = "summ_phones";
+
+        /**
+         * Flag indicating if the account is the default account for new contacts. At most one
+         * account has this flag set at a time. It can only be set to 1 on a row with null data set.
+         * <p>
+         * Type: INTEGER (boolean)
+         * @hide
+         */
+        String IS_DEFAULT = "x_is_default";
     }
 
     /**
@@ -8681,6 +8690,13 @@
          * The MIME-type of {@link #CONTENT_URI} providing a single setting.
          */
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
+
+        /**
+         * Action used to launch the UI to set the default account for new contacts.
+         */
+        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+        public static final String ACTION_SET_DEFAULT_ACCOUNT =
+                "android.provider.action.SET_DEFAULT_ACCOUNT";
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f3ebe5b..ffc98d2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19816,6 +19816,9 @@
     /**
      * Check if this view can be scrolled horizontally in a certain direction.
      *
+     * <p>This is without regard to whether the view is enabled or not, or if it will scroll
+     * in response to user input or not.
+     *
      * @param direction Negative to check scrolling left, positive to check scrolling right.
      * @return true if this view can be scrolled in the specified direction, false otherwise.
      */
@@ -19833,6 +19836,9 @@
     /**
      * Check if this view can be scrolled vertically in a certain direction.
      *
+     * <p>This is without regard to whether the view is enabled or not, or if it will scroll
+     * in response to user input or not.
+     *
      * @param direction Negative to check scrolling up, positive to check scrolling down.
      * @return true if this view can be scrolled in the specified direction, false otherwise.
      */
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ccca031..5185dc2 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -932,14 +932,20 @@
     boolean requestCursorUpdates(int cursorUpdateMode);
 
     /**
-     * Called by the {@link InputMethodManager} to enable application developers to specify a
-     * dedicated {@link Handler} on which incoming IPC method calls from input methods will be
-     * dispatched.
+     * Called by the system to enable application developers to specify a dedicated thread on which
+     * {@link InputConnection} methods are called back.
      *
-     * <p>Note: This does nothing when called from input methods.</p>
+     * <p><strong>Editor authors</strong>: although you can return your custom subclasses of
+     * {@link Handler}, the system only uses {@link android.os.Looper} returned from
+     * {@link Handler#getLooper()}.  You cannot intercept or cancel {@link InputConnection}
+     * callbacks by implementing this method.</p>
+     *
+     * <p><strong>IME authors</strong>: This method is not intended to be called from the IME.  You
+     * will always receive {@code null}.</p>
      *
      * @return {@code null} to use the default {@link Handler}.
      */
+    @Nullable
     Handler getHandler();
 
     /**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a5..e827f0a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5824,25 +5824,6 @@
         return false;
     }
 
-    /** @hide */
-    public void updateAppInfo(@NonNull ApplicationInfo info) {
-        if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) {
-            // Overlay paths are generated against a particular version of an application.
-            // The overlays paths of a newly upgraded application are incompatible with the
-            // old version of the application.
-            mApplication = info;
-        }
-        if (hasSizedRemoteViews()) {
-            for (RemoteViews layout : mSizedRemoteViews) {
-                layout.updateAppInfo(info);
-            }
-        }
-        if (hasLandscapeAndPortraitLayouts()) {
-            mLandscape.updateAppInfo(info);
-            mPortrait.updateAppInfo(info);
-        }
-    }
-
     private Context getContextForResources(Context context) {
         if (mApplication != null) {
             if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b7de7b8..1c5b39e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -94,8 +95,16 @@
     /** The container can show on top of lock screen. */
     public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
 
+    /**
+     * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
+     * used to prevent seamless rotation.
+     * TODO(b/194540864): Once we can include all windows in transition, then replace this with
+     *         something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
+     */
+    public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 7;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 8;
 
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
@@ -107,6 +116,7 @@
             FLAG_IS_VOICE_INTERACTION,
             FLAG_IS_DISPLAY,
             FLAG_OCCLUDES_KEYGUARD,
+            FLAG_DISPLAY_HAS_ALERT_WINDOWS,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -209,6 +219,10 @@
         return mOptions;
     }
 
+    /**
+     * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom
+     *         in Z (meaning index 0 is the top-most container).
+     */
     @NonNull
     public List<Change> getChanges() {
         return mChanges;
@@ -290,6 +304,9 @@
         if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
             sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD");
         }
+        if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+            sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
         }
@@ -337,6 +354,7 @@
         private ActivityManager.RunningTaskInfo mTaskInfo = null;
         private int mStartRotation = ROTATION_UNDEFINED;
         private int mEndRotation = ROTATION_UNDEFINED;
+        private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
 
         public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
             mContainer = container;
@@ -356,6 +374,7 @@
             mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
             mStartRotation = in.readInt();
             mEndRotation = in.readInt();
+            mRotationAnimation = in.readInt();
         }
 
         /** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -402,6 +421,14 @@
             mEndRotation = end;
         }
 
+        /**
+         * Sets the app-requested animation type for rotation. Will be one of the
+         * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
+         */
+        public void setRotationAnimation(int anim) {
+            mRotationAnimation = anim;
+        }
+
         /** @return the container that is changing. May be null if non-remotable (eg. activity) */
         @Nullable
         public WindowContainerToken getContainer() {
@@ -473,6 +500,11 @@
             return mEndRotation;
         }
 
+        /** @return the rotation animation. */
+        public int getRotationAnimation() {
+            return mRotationAnimation;
+        }
+
         /** @hide */
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -487,6 +519,7 @@
             dest.writeTypedObject(mTaskInfo, flags);
             dest.writeInt(mStartRotation);
             dest.writeInt(mEndRotation);
+            dest.writeInt(mRotationAnimation);
         }
 
         @NonNull
@@ -514,7 +547,7 @@
             return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
                     + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
                     + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
-                    + mStartRotation + "->" + mEndRotation + "}";
+                    + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
         }
     }
 
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 2d44054..0c27012 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -121,25 +121,54 @@
             // reportFinish() will take effect.
             return;
         }
-        closeConnection();
-
-        // Notify the app that the InputConnection was closed.
-        final View servedView = mServedView.get();
-        if (servedView != null) {
-            final Handler handler = servedView.getHandler();
-            // The handler is null if the view is already detached. When that's the case, for
-            // now, we simply don't dispatch this callback.
-            if (handler != null) {
-                if (DEBUG) {
-                    Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+        dispatch(() -> {
+            // Note that we do not need to worry about race condition here, because 1) mFinished is
+            // updated only inside this block, and 2) the code here is running on a Handler hence we
+            // assume multiple closeConnection() tasks will not be handled at the same time.
+            if (isFinished()) {
+                return;
+            }
+            Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
+            try {
+                InputConnection ic = getInputConnection();
+                // Note we do NOT check isActive() here, because this is safe
+                // for an IME to call at any time, and we need to allow it
+                // through to clean up our state after the IME has switched to
+                // another client.
+                if (ic == null) {
+                    return;
                 }
-                if (handler.getLooper().isCurrentThread()) {
-                    servedView.onInputConnectionClosedInternal();
-                } else {
-                    handler.post(servedView::onInputConnectionClosedInternal);
+                @MissingMethodFlags
+                final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
+                if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
+                    ic.closeConnection();
+                }
+            } finally {
+                synchronized (mLock) {
+                    mInputConnection = null;
+                    mFinished = true;
+                }
+                Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+            }
+
+            // Notify the app that the InputConnection was closed.
+            final View servedView = mServedView.get();
+            if (servedView != null) {
+                final Handler handler = servedView.getHandler();
+                // The handler is null if the view is already detached. When that's the case, for
+                // now, we simply don't dispatch this callback.
+                if (handler != null) {
+                    if (DEBUG) {
+                        Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+                    }
+                    if (handler.getLooper().isCurrentThread()) {
+                        servedView.onInputConnectionClosedInternal();
+                    } else {
+                        handler.post(servedView::onInputConnectionClosedInternal);
+                    }
                 }
             }
-        }
+        });
     }
 
     @Override
@@ -705,39 +734,6 @@
         });
     }
 
-    private void closeConnection() {
-        dispatch(() -> {
-            // Note that we do not need to worry about race condition here, because 1) mFinished is
-            // updated only inside this block, and 2) the code here is running on a Handler hence we
-            // assume multiple closeConnection() tasks will not be handled at the same time.
-            if (isFinished()) {
-                return;
-            }
-            Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
-            try {
-                InputConnection ic = getInputConnection();
-                // Note we do NOT check isActive() here, because this is safe
-                // for an IME to call at any time, and we need to allow it
-                // through to clean up our state after the IME has switched to
-                // another client.
-                if (ic == null) {
-                    return;
-                }
-                @MissingMethodFlags
-                final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
-                if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
-                    ic.closeConnection();
-                }
-            } finally {
-                synchronized (mLock) {
-                    mInputConnection = null;
-                    mFinished = true;
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_INPUT);
-            }
-        });
-    }
-
     @Override
     public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
             IBooleanResultCallback callback) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a6d038d..8a1f1a0 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2432,6 +2432,12 @@
     return (jint)nativeToJavaStatus(status);
 }
 
+static jint android_media_AudioSystem_setHotwordDetectionServiceUid(JNIEnv *env, jobject thiz,
+                                                                    jint uid) {
+    status_t status = AudioSystem::setHotwordDetectionServiceUid(uid);
+    return (jint)nativeToJavaStatus(status);
+}
+
 static jint
 android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArray uids) {
     std::vector<uid_t> nativeUidsVector;
@@ -2804,6 +2810,8 @@
          {"setSurroundFormatEnabled", "(IZ)I",
           (void *)android_media_AudioSystem_setSurroundFormatEnabled},
          {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid},
+         {"setHotwordDetectionServiceUid", "(I)I",
+          (void *)android_media_AudioSystem_setHotwordDetectionServiceUid},
          {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
          {"isHapticPlaybackSupported", "()Z",
           (void *)android_media_AudioSystem_isHapticPlaybackSupported},
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
index d667af3..d60c0c6 100644
--- a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
@@ -19,13 +19,25 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.annotation.DurationMillisLong;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.InputType;
 import android.text.format.DateUtils;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
@@ -45,12 +57,23 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
 /**
  * Tests for internal APIs/behaviors of {@link View} and {@link InputConnection}.
  */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ViewInputConnectionTest {
+    @DurationMillisLong
+    private static final long TIMEOUT = 5000;
+    @DurationMillisLong
+    private static final long EXPECTED_TIMEOUT = 500;
+
     @Rule
     public ActivityTestRule<ViewInputConnectionTestActivity> mActivityRule =
             new ActivityTestRule<>(ViewInputConnectionTestActivity.class);
@@ -289,4 +312,282 @@
             super.onInputConnectionClosedInternal();
         }
     }
+
+
+    @Test
+    public void testInputConnectionCallbacks_nonUiThread() throws Throwable {
+        try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread()) {
+            final ViewGroup viewGroup = getOnMainSync(() -> mActivity.findViewById(R.id.root));
+            final TestOffThreadEditor editor = getOnMainSync(() -> {
+                final TestOffThreadEditor myEditor =
+                        new TestOffThreadEditor(viewGroup.getContext(), thread.getHandler());
+                viewGroup.addView(myEditor);
+                myEditor.requestFocus();
+                return myEditor;
+            });
+
+            mInstrumentation.waitForIdleSync();
+
+            assertThat(editor.mOnCreateInputConnectionCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+                    .isTrue();
+
+            // Invalidate the currently used InputConnection by moving the focus to a new EditText.
+            mActivityRule.runOnUiThread(() -> {
+                final EditText editText = new EditText(viewGroup.getContext());
+                viewGroup.addView(editText);
+                editText.requestFocus();
+            });
+
+            // Make sure that InputConnection#closeConnection() gets called on the handler thread.
+            assertThat(editor.mInputConnectionClosedCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+                    .isTrue();
+            assertThat(editor.mInputConnectionClosedCallingThreadId.get())
+                    .isEqualTo(thread.getThreadId());
+
+            // Make sure that View#onInputConnectionClosed() is not yet dispatched, because
+            // InputConnection#closeConnection() is still blocked.
+            assertThat(editor.mOnInputConnectionClosedCalled.await(
+                    EXPECTED_TIMEOUT, TimeUnit.MILLISECONDS)).isFalse();
+
+            // Unblock InputConnection#closeConnection()
+            editor.mInputConnectionClosedBlocker.countDown();
+
+            // Make sure that View#onInputConnectionClosed() is dispatched on the main thread.
+            assertThat(editor.mOnInputConnectionClosedCalled.await(TIMEOUT, TimeUnit.MILLISECONDS))
+                    .isTrue();
+            assertThat(editor.mInputConnectionClosedBlockerTimedOut.get()).isFalse();
+            assertThat(editor.mOnInputConnectionClosedCallingThreadId.get())
+                    .isEqualTo(getOnMainSync(Process::myTid));
+        }
+    }
+
+    private <T> T getOnMainSync(@NonNull Supplier<T> supplier) throws Throwable {
+        final AtomicReference<T> result = new AtomicReference<>();
+        mActivityRule.runOnUiThread(() -> result.set(supplier.get()));
+        return result.get();
+    }
+
+    private static class TestOffThreadEditor extends View {
+        private static final int TEST_VIEW_HEIGHT = 10;
+
+        public CountDownLatch mOnCreateInputConnectionCalled = new CountDownLatch(1);
+        public CountDownLatch mInputConnectionClosedCalled = new CountDownLatch(1);
+        public CountDownLatch mInputConnectionClosedBlocker = new CountDownLatch(1);
+        public AtomicBoolean mInputConnectionClosedBlockerTimedOut = new AtomicBoolean();
+        public AtomicReference<Integer> mInputConnectionClosedCallingThreadId =
+                new AtomicReference<>();
+
+        public CountDownLatch mOnInputConnectionClosedCalled = new CountDownLatch(1);
+        public AtomicReference<Integer> mOnInputConnectionClosedCallingThreadId =
+                new AtomicReference<>();
+
+        private final Handler mInputConnectionHandler;
+
+        TestOffThreadEditor(Context context, @NonNull Handler inputConnectionHandler) {
+            super(context);
+            setBackgroundColor(Color.YELLOW);
+            setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, TEST_VIEW_HEIGHT));
+            setFocusableInTouchMode(true);
+            setFocusable(true);
+            mInputConnectionHandler = inputConnectionHandler;
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            mOnCreateInputConnectionCalled.countDown();
+            outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+            return new NoOpInputConnection() {
+                @Override
+                public Handler getHandler() {
+                    return mInputConnectionHandler;
+                }
+
+                @Override
+                public void closeConnection() {
+                    mInputConnectionClosedCallingThreadId.compareAndSet(null, Process.myTid());
+                    mInputConnectionClosedCalled.countDown();
+                    try {
+                        if (mInputConnectionClosedBlocker.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                            return;
+                        }
+                    } catch (InterruptedException e) {
+                    }
+                    mInputConnectionClosedBlockerTimedOut.set(true);
+                }
+            };
+        }
+
+        @Override
+        public boolean onCheckIsTextEditor() {
+            return true;
+        }
+
+        @Override
+        public void onInputConnectionClosedInternal() {
+            mOnInputConnectionClosedCallingThreadId.compareAndSet(null, Process.myTid());
+            mOnInputConnectionClosedCalled.countDown();
+            super.onInputConnectionClosedInternal();
+        }
+    }
+
+    static class NoOpInputConnection implements InputConnection {
+
+        @Override
+        public CharSequence getTextBeforeCursor(int n, int flags) {
+            return null;
+        }
+
+        @Override
+        public CharSequence getTextAfterCursor(int n, int flags) {
+            return null;
+        }
+
+        @Override
+        public CharSequence getSelectedText(int flags) {
+            return null;
+        }
+
+        @Override
+        public int getCursorCapsMode(int reqModes) {
+            return 0;
+        }
+
+        @Override
+        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+            return null;
+        }
+
+        @Override
+        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+            return false;
+        }
+
+        @Override
+        public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+            return false;
+        }
+
+        @Override
+        public boolean setComposingText(CharSequence text, int newCursorPosition) {
+            return false;
+        }
+
+        @Override
+        public boolean setComposingRegion(int start, int end) {
+            return false;
+        }
+
+        @Override
+        public boolean finishComposingText() {
+            return false;
+        }
+
+        @Override
+        public boolean commitText(CharSequence text, int newCursorPosition) {
+            return false;
+        }
+
+        @Override
+        public boolean commitCompletion(CompletionInfo text) {
+            return false;
+        }
+
+        @Override
+        public boolean commitCorrection(CorrectionInfo correctionInfo) {
+            return false;
+        }
+
+        @Override
+        public boolean setSelection(int start, int end) {
+            return false;
+        }
+
+        @Override
+        public boolean performEditorAction(int editorAction) {
+            return false;
+        }
+
+        @Override
+        public boolean performContextMenuAction(int id) {
+            return false;
+        }
+
+        @Override
+        public boolean beginBatchEdit() {
+            return false;
+        }
+
+        @Override
+        public boolean endBatchEdit() {
+            return false;
+        }
+
+        @Override
+        public boolean sendKeyEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean clearMetaKeyStates(int states) {
+            return false;
+        }
+
+        @Override
+        public boolean reportFullscreenMode(boolean enabled) {
+            return false;
+        }
+
+        @Override
+        public boolean performPrivateCommand(String action, Bundle data) {
+            return false;
+        }
+
+        @Override
+        public boolean requestCursorUpdates(int cursorUpdateMode) {
+            return false;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return null;
+        }
+
+        @Override
+        public void closeConnection() {
+
+        }
+
+        @Override
+        public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+            return false;
+        }
+    }
+
+    private static final class InputConnectionHandlingThread extends HandlerThread
+            implements AutoCloseable {
+
+        private final Handler mHandler;
+
+        InputConnectionHandlingThread() {
+            super("IC-callback");
+            start();
+            mHandler = Handler.createAsync(getLooper());
+        }
+
+        @NonNull
+        Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public void close() {
+            quitSafely();
+            try {
+                join(TIMEOUT);
+            } catch (InterruptedException e) {
+                fail("Failed to stop the thread: " + e);
+            }
+        }
+    }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index d3cff5c..7354c90 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -49,7 +49,9 @@
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.os.Build;
+import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 
@@ -113,6 +115,7 @@
  * @attr ref android.R.styleable#RippleDrawable_color
  */
 public class RippleDrawable extends LayerDrawable {
+    private static final String TAG = "RippleDrawable";
     /**
      * Radius value that specifies the ripple radius should be computed based
      * on the size of the ripple's container.
@@ -848,6 +851,10 @@
 
     private void startBackgroundAnimation() {
         mRunBackgroundAnimation = false;
+        if (Looper.myLooper() == null) {
+            Log.w(TAG, "Thread doesn't have a looper. Skipping animation.");
+            return;
+        }
         mBackgroundAnimation = ValueAnimator.ofFloat(mBackgroundOpacity, mTargetBackgroundOpacity);
         mBackgroundAnimation.setInterpolator(LINEAR_INTERPOLATOR);
         mBackgroundAnimation.setDuration(BACKGROUND_OPACITY_DURATION);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index d1fbf31..9bbede3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -24,6 +24,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -47,6 +48,7 @@
     private final Optional<AppPairsController> mAppPairsOptional;
     private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
+    private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
     private final StartingWindowController mStartingWindow;
@@ -62,6 +64,7 @@
             Optional<AppPairsController> appPairsOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
+            Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
             Transitions transitions,
             StartingWindowController startingWindow,
             ShellExecutor mainExecutor) {
@@ -74,6 +77,7 @@
         mAppPairsOptional = appPairsOptional;
         mFullscreenTaskListener = fullscreenTaskListener;
         mPipTouchHandlerOptional = pipTouchHandlerOptional;
+        mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
         mTransitions = transitions;
         mMainExecutor = mainExecutor;
         mStartingWindow = startingWindow;
@@ -108,6 +112,11 @@
         // controller instead of the feature interface, can just initialize the touch handler if
         // needed
         mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
+
+        // Initialize optional freeform
+        mFreeformTaskListenerOptional.ifPresent(f ->
+                mShellTaskOrganizer.addListenerForType(
+                        f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
     }
 
     @ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6d..ab8a21c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -71,12 +71,14 @@
     public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
     public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
     public static final int TASK_LISTENER_TYPE_PIP = -4;
+    public static final int TASK_LISTENER_TYPE_FREEFORM = -5;
 
     @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
             TASK_LISTENER_TYPE_UNDEFINED,
             TASK_LISTENER_TYPE_FULLSCREEN,
             TASK_LISTENER_TYPE_MULTI_WINDOW,
             TASK_LISTENER_TYPE_PIP,
+            TASK_LISTENER_TYPE_FREEFORM,
     })
     public @interface TaskListenerType {}
 
@@ -572,6 +574,7 @@
             case WINDOWING_MODE_PINNED:
                 return TASK_LISTENER_TYPE_PIP;
             case WINDOWING_MODE_FREEFORM:
+                return TASK_LISTENER_TYPE_FREEFORM;
             case WINDOWING_MODE_UNDEFINED:
             default:
                 return TASK_LISTENER_TYPE_UNDEFINED;
@@ -586,6 +589,8 @@
                 return "TASK_LISTENER_TYPE_MULTI_WINDOW";
             case TASK_LISTENER_TYPE_PIP:
                 return "TASK_LISTENER_TYPE_PIP";
+            case TASK_LISTENER_TYPE_FREEFORM:
+                return "TASK_LISTENER_TYPE_FREEFORM";
             case TASK_LISTENER_TYPE_UNDEFINED:
                 return "TASK_LISTENER_TYPE_UNDEFINED";
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index b7235a3..b90283f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -82,6 +82,9 @@
     private boolean mHasNavigationBar = false;
     private boolean mHasStatusBar = false;
     private int mNavBarFrameHeight = 0;
+    private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
+    private boolean mNavigationBarCanMove = false;
+    private boolean mReverseDefaultRotation = false;
 
     @Override
     public boolean equals(Object o) {
@@ -98,6 +101,10 @@
                 && Objects.equals(mStableInsets, other.mStableInsets)
                 && mHasNavigationBar == other.mHasNavigationBar
                 && mHasStatusBar == other.mHasStatusBar
+                && mAllowSeamlessRotationDespiteNavBarMoving
+                        == other.mAllowSeamlessRotationDespiteNavBarMoving
+                && mNavigationBarCanMove == other.mNavigationBarCanMove
+                && mReverseDefaultRotation == other.mReverseDefaultRotation
                 && mNavBarFrameHeight == other.mNavBarFrameHeight;
     }
 
@@ -105,7 +112,8 @@
     public int hashCode() {
         return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
                 mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
-                mNavBarFrameHeight);
+                mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+                mNavigationBarCanMove, mReverseDefaultRotation);
     }
 
     /**
@@ -150,6 +158,9 @@
         mDensityDpi = dl.mDensityDpi;
         mHasNavigationBar = dl.mHasNavigationBar;
         mHasStatusBar = dl.mHasStatusBar;
+        mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
+        mNavigationBarCanMove = dl.mNavigationBarCanMove;
+        mReverseDefaultRotation = dl.mReverseDefaultRotation;
         mNavBarFrameHeight = dl.mNavBarFrameHeight;
         mNonDecorInsets.set(dl.mNonDecorInsets);
         mStableInsets.set(dl.mStableInsets);
@@ -165,6 +176,10 @@
         mDensityDpi = info.logicalDensityDpi;
         mHasNavigationBar = hasNavigationBar;
         mHasStatusBar = hasStatusBar;
+        mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
+            R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+        mNavigationBarCanMove = res.getBoolean(R.bool.config_navBarCanMove);
+        mReverseDefaultRotation = res.getBoolean(R.bool.config_reverseDefaultRotation);
         recalcInsets(res);
     }
 
@@ -249,6 +264,28 @@
         return mNavBarFrameHeight;
     }
 
+    /** @return whether we can seamlessly rotate even if nav-bar can change sides. */
+    public boolean allowSeamlessRotationDespiteNavBarMoving() {
+        return mAllowSeamlessRotationDespiteNavBarMoving;
+    }
+
+    /** @return whether the navigation bar will change sides during rotation. */
+    public boolean navigationBarCanMove() {
+        return mNavigationBarCanMove;
+    }
+
+    /** @return the rotation that would make the physical display "upside down". */
+    public int getUpsideDownRotation() {
+        boolean displayHardwareIsLandscape = mWidth > mHeight;
+        if ((mRotation % 2) != 0) {
+            displayHardwareIsLandscape = !displayHardwareIsLandscape;
+        }
+        if (displayHardwareIsLandscape) {
+            return mReverseDefaultRotation ? Surface.ROTATION_270 : Surface.ROTATION_90;
+        }
+        return Surface.ROTATION_180;
+    }
+
     /** Gets the orientation of this layout */
     public int getOrientation() {
         return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
new file mode 100644
index 0000000..5fb3297
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wm.shell.freeform;
+
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link ShellTaskOrganizer.TaskListener} for {@link
+ * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
+ */
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+    private static final String TAG = "FreeformTaskListener";
+
+    private final SyncTransactionQueue mSyncQueue;
+
+    private final SparseArray<State> mTasks = new SparseArray<>();
+
+    private static class State {
+        RunningTaskInfo mTaskInfo;
+        SurfaceControl mLeash;
+    }
+
+    public FreeformTaskListener(SyncTransactionQueue syncQueue) {
+        mSyncQueue = syncQueue;
+    }
+
+    @Override
+    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        if (mTasks.get(taskInfo.taskId) != null) {
+            throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
+                taskInfo.taskId);
+        final State state = new State();
+        state.mTaskInfo = taskInfo;
+        state.mLeash = leash;
+        mTasks.put(taskInfo.taskId, state);
+
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        mSyncQueue.runInSync(t -> {
+            Point taskPosition = taskInfo.positionInParent;
+            t.setPosition(leash, taskPosition.x, taskPosition.y)
+                    .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+                    .show(leash);
+        });
+    }
+
+    @Override
+    public void onTaskVanished(RunningTaskInfo taskInfo) {
+        State state = mTasks.get(taskInfo.taskId);
+        if (state == null) {
+            Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+            return;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
+                taskInfo.taskId);
+        mTasks.remove(taskInfo.taskId);
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        State state = mTasks.get(taskInfo.taskId);
+        if (state == null) {
+            throw new RuntimeException(
+                    "Task info changed before appearing: #" + taskInfo.taskId);
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
+                taskInfo.taskId);
+        state.mTaskInfo = taskInfo;
+
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final SurfaceControl leash = state.mLeash;
+        mSyncQueue.runInSync(t -> {
+            Point taskPosition = taskInfo.positionInParent;
+            t.setPosition(leash, taskPosition.x, taskPosition.y)
+                    .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+                    .show(leash);
+        });
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + this);
+        pw.println(innerPrefix + mTasks.size() + " tasks");
+    }
+
+    @Override
+    public String toString() {
+        return TAG;
+    }
+
+    /**
+     * Checks if freeform support is enabled in system.
+     *
+     * @param context context used to check settings and package manager.
+     * @return {@code true} if freeform is enabled, {@code false} if not.
+     */
+    public static boolean isFreeformEnabled(Context context) {
+        return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+                || Settings.Global.getInt(context.getContentResolver(),
+                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+    }
+
+    /**
+     * Creates {@link FreeformTaskListener} if freeform is enabled.
+     */
+    public static FreeformTaskListener create(Context context,
+            SyncTransactionQueue syncQueue) {
+        if (!isFreeformEnabled(context)) {
+            return null;
+        }
+
+        return new FreeformTaskListener(syncQueue);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 21bc889..ff333c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -130,7 +130,7 @@
      */
     public boolean getSettingsTapsAppToExit(ContentResolver resolver, int userId) {
         return Settings.Secure.getIntForUser(resolver,
-                Settings.Secure.TAPS_APP_TO_EXIT, 0, userId) == 1;
+                Settings.Secure.TAPS_APP_TO_EXIT, 1, userId) == 1;
     }
 
     /**
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 01134a7..b584038 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
@@ -23,6 +23,10 @@
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
@@ -31,8 +35,10 @@
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -68,9 +74,12 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -97,6 +106,7 @@
             SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
 
     private final TransactionPool mTransactionPool;
+    private final DisplayController mDisplayController;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
@@ -114,8 +124,10 @@
 
     private ScreenRotationAnimation mRotationAnimation;
 
-    DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
+    DefaultTransitionHandler(@NonNull DisplayController displayController,
+            @NonNull TransactionPool transactionPool, Context context,
             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+        mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
         mMainExecutor = mainExecutor;
@@ -126,6 +138,110 @@
         AttributeCache.init(context);
     }
 
+    @VisibleForTesting
+    static boolean isRotationSeamless(@NonNull TransitionInfo info,
+            DisplayController displayController) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "Display is rotating, check if it should be seamless.");
+        boolean checkedDisplayLayout = false;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+
+            // Only look at changing things. showing/hiding don't need to rotate.
+            if (change.getMode() != TRANSIT_CHANGE) continue;
+
+            // This container isn't rotating, so we can ignore it.
+            if (change.getEndRotation() == change.getStartRotation()) continue;
+
+            if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+                // In the presence of System Alert windows we can not seamlessly rotate.
+                if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "  display has system alert windows, so not seamless.");
+                    return false;
+                }
+            } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+                if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "  wallpaper is participating but isn't seamless.");
+                    return false;
+                }
+            } else if (change.getTaskInfo() != null) {
+                // We only enable seamless rotation if all the visible task windows requested it.
+                if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "  task %s isn't requesting seamless, so not seamless.",
+                            change.getTaskInfo().taskId);
+                    return false;
+                }
+
+                // This is the only way to get display-id currently, so we will check display
+                // capabilities here
+                if (!checkedDisplayLayout) {
+                    // only need to check display once.
+                    checkedDisplayLayout = true;
+                    final DisplayLayout displayLayout = displayController.getDisplayLayout(
+                            change.getTaskInfo().displayId);
+                    // For the upside down rotation we don't rotate seamlessly as the navigation
+                    // bar moves position. Note most apps (using orientation:sensor or user as
+                    // opposed to fullSensor) will not enter the reverse portrait orientation, so
+                    // actually the orientation won't change at all.
+                    int upsideDownRotation = displayLayout.getUpsideDownRotation();
+                    if (change.getStartRotation() == upsideDownRotation
+                            || change.getEndRotation() == upsideDownRotation) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                                "  rotation involves upside-down portrait, so not seamless.");
+                        return false;
+                    }
+
+                    // If the navigation bar can't change sides, then it will jump when we change
+                    // orientations and we don't rotate seamlessly - unless that is allowed, eg.
+                    // with gesture navigation where the navbar is low-profile enough that this
+                    // isn't very noticeable.
+                    if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+                            && (!(displayLayout.navigationBarCanMove()
+                                    && (change.getStartAbsBounds().width()
+                                            != change.getStartAbsBounds().height())))) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                                "  nav bar changes sides, so not seamless.");
+                        return false;
+                    }
+                }
+            }
+        }
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
+        return true;
+    }
+
+    /**
+     * Gets the rotation animation for the topmost task. Assumes that seamless is checked
+     * elsewhere, so it will default SEAMLESS to ROTATE.
+     */
+    private int getRotationAnimation(@NonNull TransitionInfo info) {
+        // Traverse in top-to-bottom order so that the first task is top-most
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+
+            // Only look at changing things. showing/hiding don't need to rotate.
+            if (change.getMode() != TRANSIT_CHANGE) continue;
+
+            // This container isn't rotating, so we can ignore it.
+            if (change.getEndRotation() == change.getStartRotation()) continue;
+
+            if (change.getTaskInfo() != null) {
+                final int anim = change.getRotationAnimation();
+                if (anim == ROTATION_ANIMATION_UNSPECIFIED
+                        // Fallback animation for seamless should also be default.
+                        || anim == ROTATION_ANIMATION_SEAMLESS) {
+                    return ROTATION_ANIMATION_ROTATE;
+                }
+                return anim;
+            }
+        }
+        return ROTATION_ANIMATION_ROTATE;
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -168,11 +284,15 @@
             if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
                     && (change.getEndRotation() != change.getStartRotation())
                     && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
-                mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                        mTransactionPool, startTransaction, change, info.getRootLeash());
-                mRotationAnimation.startAnimation(animations, onAnimFinish,
-                        mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
-                continue;
+                boolean isSeamless = isRotationSeamless(info, mDisplayController);
+                final int anim = getRotationAnimation(info);
+                if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
+                    mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
+                            mTransactionPool, startTransaction, change, info.getRootLeash());
+                    mRotationAnimation.startAnimation(animations, onAnimFinish,
+                            mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+                    continue;
+                }
             }
 
             if (change.getMode() == TRANSIT_CHANGE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 01ef2a6..09dfabf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -54,6 +54,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
@@ -113,15 +114,16 @@
     private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
 
     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
-            @NonNull Context context, @NonNull ShellExecutor mainExecutor,
-            @NonNull ShellExecutor animExecutor) {
+            @NonNull DisplayController displayController, @NonNull Context context,
+            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
         mOrganizer = organizer;
         mContext = context;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
         mPlayerImpl = new TransitionPlayerImpl();
         // The very last handler (0 in the list) should be the default one.
-        mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
+        mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
+                animExecutor));
         // Next lowest priority is remote transitions.
         mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
         mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 21329c7..84565e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -20,12 +20,18 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -49,6 +55,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.IDisplayWindowListener;
+import android.view.IWindowManager;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
@@ -66,12 +75,15 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 
@@ -98,8 +110,7 @@
 
     @Test
     public void testBasicTransitionFlow() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         IBinder transitToken = new Binder();
@@ -118,8 +129,7 @@
 
     @Test
     public void testNonDefaultHandler() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -202,8 +212,7 @@
 
     @Test
     public void testRequestRemoteTransition() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         final boolean[] remoteCalled = new boolean[]{false};
@@ -297,8 +306,7 @@
 
     @Test
     public void testRegisteredRemoteTransition() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         final boolean[] remoteCalled = new boolean[]{false};
@@ -343,8 +351,7 @@
 
     @Test
     public void testOneShotRemoteHandler() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         final boolean[] remoteCalled = new boolean[]{false};
@@ -390,8 +397,7 @@
 
     @Test
     public void testTransitionQueueing() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
         IBinder transitToken1 = new Binder();
@@ -431,8 +437,7 @@
 
     @Test
     public void testTransitionMerging() {
-        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
-                mMainExecutor, mAnimExecutor);
+        Transitions transitions = createTestTransitions();
         mDefaultHandler.setSimulateMerge(true);
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -468,6 +473,63 @@
         assertEquals(0, mDefaultHandler.activeCount());
     }
 
+    @Test
+    public void testShouldRotateSeamlessly() throws Exception {
+        final RunningTaskInfo taskInfo =
+                createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final RunningTaskInfo taskInfoPip =
+                createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+        final DisplayController displays = createTestDisplayController();
+        final @Surface.Rotation int upsideDown = displays
+                .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+
+        final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+                        .build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
+                .build();
+        assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+
+        // Seamless if all tasks are seamless
+        final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+                        .build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+                .build();
+        assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+
+        // Not seamless if there is PiP (or any other non-seamless task)
+        final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+                        .build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
+                        .setRotate().build())
+                .build();
+        assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
+
+        // Not seamless if one of rotations is upside-down
+        final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+                        .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
+                .build();
+        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
+
+        // Not seamless if system alert windows
+        final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
+                        FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+                .build();
+        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
+    }
+
     class TransitionInfoBuilder {
         final TransitionInfo mInfo;
 
@@ -490,11 +552,53 @@
             return addChange(mode, null /* taskInfo */);
         }
 
+        TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+            mInfo.addChange(change);
+            return this;
+        }
+
         TransitionInfo build() {
             return mInfo;
         }
     }
 
+    class ChangeBuilder {
+        final TransitionInfo.Change mChange;
+
+        ChangeBuilder(@WindowManager.TransitionType int mode) {
+            mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+            mChange.setMode(mode);
+        }
+
+        ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
+            mChange.setFlags(flags);
+            return this;
+        }
+
+        ChangeBuilder setTask(RunningTaskInfo taskInfo) {
+            mChange.setTaskInfo(taskInfo);
+            return this;
+        }
+
+        ChangeBuilder setRotate(int anim) {
+            return setRotate(Surface.ROTATION_90, anim);
+        }
+
+        ChangeBuilder setRotate() {
+            return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
+        }
+
+        ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
+            mChange.setRotation(Surface.ROTATION_0, target);
+            mChange.setRotationAnimation(anim);
+            return this;
+        }
+
+        TransitionInfo.Change build() {
+            return mChange;
+        }
+    }
+
     class TestTransitionHandler implements Transitions.TransitionHandler {
         ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
         final ArrayList<IBinder> mMerged = new ArrayList<>();
@@ -566,4 +670,45 @@
         return taskInfo;
     }
 
+    private DisplayController createTestDisplayController() {
+        IWindowManager mockWM = mock(IWindowManager.class);
+        final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
+        try {
+            doAnswer(new Answer() {
+                @Override
+                public Object answer(InvocationOnMock invocation) {
+                    displayListener[0] = invocation.getArgument(0);
+                    return null;
+                }
+            }).when(mockWM).registerDisplayWindowListener(any());
+        } catch (RemoteException e) {
+            // No remote stuff happening, so this can't be hit
+        }
+        DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
+        try {
+            displayListener[0].onDisplayAdded(DEFAULT_DISPLAY);
+            mMainExecutor.flushAll();
+        } catch (RemoteException e) {
+            // Again, no remote stuff
+        }
+        return out;
+    }
+
+    private Transitions createTestTransitions() {
+        return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
+                mContext, mMainExecutor, mAnimExecutor);
+    }
+//
+//    private class TestDisplayController extends DisplayController {
+//        private final DisplayLayout mTestDisplayLayout;
+//        TestDisplayController() {
+//            super(mContext, mock(IWindowManager.class), mMainExecutor);
+//            mTestDisplayLayout = new DisplayLayout();
+//            mTestDisplayLayout.
+//        }
+//
+//        @Override
+//        DisplayLayout
+//    }
+
 }
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 6cb53206..8d112d1 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -21,6 +21,7 @@
 // TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
 #include <surfacetexture/surface_texture_platform.h>
 #include "AutoBackendTextureRelease.h"
+#include "Matrix.h"
 #include "Properties.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/EglManager.h"
@@ -144,17 +145,16 @@
         }
         if (mUpdateTexImage) {
             mUpdateTexImage = false;
+            float transformMatrix[16];
             android_dataspace dataspace;
             int slot;
             bool newContent = false;
-            ARect rect;
-            uint32_t textureTransform;
             // Note: ASurfaceTexture_dequeueBuffer discards all but the last frame. This
             // is necessary if the SurfaceTexture queue is in synchronous mode, and we
             // cannot tell which mode it is in.
             AHardwareBuffer* hardwareBuffer = ASurfaceTexture_dequeueBuffer(
-                    mSurfaceTexture.get(), &slot, &dataspace, &newContent, createReleaseFence,
-                    fenceWait, this, &rect, &textureTransform);
+                    mSurfaceTexture.get(), &slot, &dataspace, transformMatrix, &newContent,
+                    createReleaseFence, fenceWait, this);
 
             if (hardwareBuffer) {
                 mCurrentSlot = slot;
@@ -165,12 +165,12 @@
                 // (invoked by createIfNeeded) will add a ref to the AHardwareBuffer.
                 AHardwareBuffer_release(hardwareBuffer);
                 if (layerImage.get()) {
+                    SkMatrix textureTransform;
+                    mat4(transformMatrix).copyTo(textureTransform);
                     // force filtration if buffer size != layer size
                     bool forceFilter =
                             mWidth != layerImage->width() || mHeight != layerImage->height();
-                    SkRect cropRect =
-                            SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
-                    updateLayer(forceFilter, textureTransform, cropRect, layerImage);
+                    updateLayer(forceFilter, textureTransform, layerImage);
                 }
             }
         }
@@ -182,13 +182,12 @@
     }
 }
 
-void DeferredLayerUpdater::updateLayer(bool forceFilter, const uint32_t textureTransform,
-                                       const SkRect cropRect, const sk_sp<SkImage>& layerImage) {
+void DeferredLayerUpdater::updateLayer(bool forceFilter, const SkMatrix& textureTransform,
+                                       const sk_sp<SkImage>& layerImage) {
     mLayer->setBlend(mBlend);
     mLayer->setForceFilter(forceFilter);
     mLayer->setSize(mWidth, mHeight);
-    mLayer->setTextureTransform(textureTransform);
-    mLayer->setCropRect(cropRect);
+    mLayer->getTexTransform() = textureTransform;
     mLayer->setImage(layerImage);
 }
 
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 7a63ccd..8f79c4e 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -90,7 +90,7 @@
 
     void detachSurfaceTexture();
 
-    void updateLayer(bool forceFilter, const uint32_t textureTransform, const SkRect cropRect,
+    void updateLayer(bool forceFilter, const SkMatrix& textureTransform,
                      const sk_sp<SkImage>& layerImage);
 
     void destroyLayer();
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 9053c12..47c47e0 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -35,6 +35,7 @@
     // preserves the old inc/dec ref locations. This should be changed...
     incStrong(nullptr);
     renderState.registerLayer(this);
+    texTransform.setIdentity();
     transform.setIdentity();
 }
 
@@ -100,6 +101,7 @@
     const int layerHeight = getHeight();
     if (layerImage) {
         SkMatrix textureMatrixInv;
+        textureMatrixInv = getTexTransform();
         // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
         // use bottom left origin and remove flipV and invert transformations.
         SkMatrix flipV;
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 656a817..e99e762 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -74,18 +74,10 @@
 
     void setColorFilter(sk_sp<SkColorFilter> filter) { mColorFilter = filter; };
 
+    inline SkMatrix& getTexTransform() { return texTransform; }
+
     inline SkMatrix& getTransform() { return transform; }
 
-    inline SkRect getCropRect() { return mCropRect; }
-
-    inline void setCropRect(const SkRect cropRect) { mCropRect = cropRect; }
-
-    inline void setTextureTransform(uint32_t textureTransform) {
-        mTextureTransform = textureTransform;
-    }
-
-    inline uint32_t getTextureTransform() { return mTextureTransform; }
-
     /**
      * Posts a decStrong call to the appropriate thread.
      * Thread-safe.
@@ -124,21 +116,16 @@
     SkBlendMode mode;
 
     /**
+     * Optional texture coordinates transform.
+     */
+    SkMatrix texTransform;
+
+    /**
      * Optional transform.
      */
     SkMatrix transform;
 
     /**
-     * Optional crop
-     */
-    SkRect mCropRect;
-
-    /**
-     * Optional transform
-     */
-    uint32_t mTextureTransform;
-
-    /**
      * An image backing the layer.
      */
     sk_sp<SkImage> layerImage;
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 386c88a..a743d30 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -251,6 +251,8 @@
 
     Rect srcRect;
     Matrix4 transform;
+    transform.loadScale(1, -1, 1);
+    transform.translate(0, -1);
 
     return copyImageInto(hwBitmap->makeImage(), transform, srcRect, bitmap);
 }
@@ -278,6 +280,8 @@
 CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
     Rect srcRect;
     Matrix4 transform;
+    transform.loadScale(1, -1, 1);
+    transform.translate(0, -1);
     return copyImageInto(image, transform, srcRect, bitmap);
 }
 
@@ -316,6 +320,7 @@
 
     Layer layer(mRenderThread.renderState(), nullptr, 255, SkBlendMode::kSrc);
     layer.setSize(displayedWidth, displayedHeight);
+    texTransform.copyTo(layer.getTexTransform());
     layer.setImage(image);
     // Scaling filter is not explicitly set here, because it is done inside copyLayerInfo
     // after checking the necessity based on the src/dest rect size and the transformation.
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index b28277c..e32788c 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -18,11 +18,9 @@
 #include <utils/MathUtils.h>
 
 #include "GrBackendSurface.h"
-#include "Matrix.h"
 #include "SkColorFilter.h"
 #include "SkSurface.h"
 #include "gl/GrGLTypes.h"
-#include "system/window.h"
 
 namespace android {
 namespace uirenderer {
@@ -31,8 +29,7 @@
 void LayerDrawable::onDraw(SkCanvas* canvas) {
     Layer* layer = mLayerUpdater->backingLayer();
     if (layer) {
-        SkRect srcRect = layer->getCropRect();
-        DrawLayer(canvas->recordingContext(), canvas, layer, &srcRect, nullptr, true);
+        DrawLayer(canvas->recordingContext(), canvas, layer, nullptr, nullptr, true);
     }
 }
 
@@ -82,16 +79,33 @@
         return false;
     }
     // transform the matrix based on the layer
-    const uint32_t transform = layer->getTextureTransform();
+    SkMatrix layerTransform = layer->getTransform();
     sk_sp<SkImage> layerImage = layer->getImage();
     const int layerWidth = layer->getWidth();
     const int layerHeight = layer->getHeight();
-    SkMatrix layerTransform = layer->getTransform();
+
     if (layerImage) {
+        SkMatrix textureMatrixInv;
+        textureMatrixInv = layer->getTexTransform();
+        // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+        // use bottom left origin and remove flipV and invert transformations.
+        SkMatrix flipV;
+        flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+        textureMatrixInv.preConcat(flipV);
+        textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight);
+        textureMatrixInv.postScale(layerImage->width(), layerImage->height());
+        SkMatrix textureMatrix;
+        if (!textureMatrixInv.invert(&textureMatrix)) {
+            textureMatrix = textureMatrixInv;
+        }
+
         SkMatrix matrix;
         if (useLayerTransform) {
-            matrix = layerTransform;
+            matrix = SkMatrix::Concat(layerTransform, textureMatrix);
+        } else {
+            matrix = textureMatrix;
         }
+
         SkPaint paint;
         paint.setAlpha(layer->getAlpha());
         paint.setBlendMode(layer->getMode());
@@ -101,54 +115,39 @@
             canvas->save();
             canvas->concat(matrix);
         }
-        const SkMatrix totalMatrix = canvas->getTotalMatrix();
+        const SkMatrix& totalMatrix = canvas->getTotalMatrix();
         if (dstRect || srcRect) {
             SkMatrix matrixInv;
             if (!matrix.invert(&matrixInv)) {
                 matrixInv = matrix;
             }
             SkRect skiaSrcRect;
-            if (srcRect && !srcRect->isEmpty()) {
+            if (srcRect) {
                 skiaSrcRect = *srcRect;
             } else {
-                skiaSrcRect = (transform & NATIVE_WINDOW_TRANSFORM_ROT_90)
-                                      ? SkRect::MakeIWH(layerHeight, layerWidth)
-                                      : SkRect::MakeIWH(layerWidth, layerHeight);
+                skiaSrcRect = SkRect::MakeIWH(layerWidth, layerHeight);
             }
             matrixInv.mapRect(&skiaSrcRect);
             SkRect skiaDestRect;
-            if (dstRect && !dstRect->isEmpty()) {
+            if (dstRect) {
                 skiaDestRect = *dstRect;
             } else {
                 skiaDestRect = SkRect::MakeIWH(layerWidth, layerHeight);
             }
             matrixInv.mapRect(&skiaDestRect);
+            // If (matrix is a rect-to-rect transform)
+            // and (src/dst buffers size match in screen coordinates)
+            // and (src/dst corners align fractionally),
+            // then use nearest neighbor, otherwise use bilerp sampling.
+            // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works
+            // only for SrcOver blending and without color filter (readback uses Src blending).
             SkSamplingOptions sampling(SkFilterMode::kNearest);
             if (layer->getForceFilter() ||
                 shouldFilterRect(totalMatrix, skiaSrcRect, skiaDestRect)) {
                 sampling = SkSamplingOptions(SkFilterMode::kLinear);
             }
-
-            const float px = skiaDestRect.centerX();
-            const float py = skiaDestRect.centerY();
-            if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
-                matrix.postScale(-1.f, 1.f, px, py);
-            }
-            if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
-                matrix.postScale(1.f, -1.f, px, py);
-            }
-            if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-                matrix.postRotate(90, 0, 0);
-                matrix.postTranslate(skiaDestRect.height(), 0);
-            }
-            auto constraint = SkCanvas::kFast_SrcRectConstraint;
-            if (srcRect && srcRect->isEmpty()) {
-                constraint = SkCanvas::kStrict_SrcRectConstraint;
-            }
-            matrix.postConcat(SkMatrix::MakeRectToRect(skiaSrcRect, skiaDestRect,
-                                                       SkMatrix::kFill_ScaleToFit));
             canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
-                                  constraint);
+                                  SkCanvas::kFast_SrcRectConstraint);
         } else {
             SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
             SkSamplingOptions sampling(SkFilterMode::kNearest);
@@ -162,6 +161,7 @@
             canvas->restore();
         }
     }
+
     return layerImage != nullptr;
 }
 
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 1bfdc47..e8ba15f 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -74,7 +74,7 @@
     layerUpdater->setTransform(&transform);
 
     // updateLayer so it's ready to draw
-    layerUpdater->updateLayer(true, 0, SkRect::MakeEmpty(), nullptr);
+    layerUpdater->updateLayer(true, SkMatrix::I(), nullptr);
     return layerUpdater;
 }
 
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index ca84aee..955a5e7 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -36,20 +36,19 @@
     EXPECT_EQ(0u, layerUpdater->backingLayer()->getHeight());
     EXPECT_FALSE(layerUpdater->backingLayer()->getForceFilter());
     EXPECT_FALSE(layerUpdater->backingLayer()->isBlend());
+    EXPECT_EQ(Matrix4::identity(), layerUpdater->backingLayer()->getTexTransform());
 
     // push the deferred updates to the layer
-    uint32_t textureTransform = 1;
+    SkMatrix scaledMatrix = SkMatrix::Scale(0.5, 0.5);
     SkBitmap bitmap;
     bitmap.allocN32Pixels(16, 16);
-    SkRect cropRect = SkRect::MakeIWH(10, 10);
     sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
-    layerUpdater->updateLayer(true, textureTransform, cropRect, layerImage);
+    layerUpdater->updateLayer(true, scaledMatrix, layerImage);
 
     // the backing layer should now have all the properties applied.
     EXPECT_EQ(100u, layerUpdater->backingLayer()->getWidth());
     EXPECT_EQ(100u, layerUpdater->backingLayer()->getHeight());
     EXPECT_TRUE(layerUpdater->backingLayer()->getForceFilter());
     EXPECT_TRUE(layerUpdater->backingLayer()->isBlend());
-    EXPECT_EQ(textureTransform, layerUpdater->backingLayer()->getTextureTransform());
-    EXPECT_EQ(cropRect, layerUpdater->backingLayer()->getCropRect());
+    EXPECT_EQ(scaledMatrix, layerUpdater->backingLayer()->getTexTransform());
 }
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index c827932..cb887f2 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -38,6 +38,17 @@
 
     public abstract void updateRingerModeAffectedStreamsInternal();
 
+    /**
+     * Notify the UID of the currently active {@link android.service.voice.HotwordDetectionService}.
+     *
+     * <p>The caller is expected to take care of any performance implications, e.g. by using a
+     * background thread to call this method.</p>
+     *
+     * @param uid UID of the currently active service or {@link android.os.Process#INVALID_UID} if
+     *            none.
+     */
+    public abstract void setHotwordDetectionServiceUid(int uid);
+
     public abstract void setAccessibilityServiceUids(IntArray uids);
 
     /**
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 321db4e..5bd6891 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1769,6 +1769,13 @@
     public static native int setAssistantUid(int uid);
 
     /**
+     * Communicate UID of the current {@link android.service.voice.HotwordDetectionService} to audio
+     * policy service.
+     * @hide
+     */
+    public static native int setHotwordDetectionServiceUid(int uid);
+
+    /**
      * @hide
      * Communicate UIDs of active accessibility services to audio policy service.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 79446e4..ed8d524 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,6 +44,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -56,6 +58,7 @@
 /**
  * InfoMediaManager provide interface to get InfoMediaDevice list.
  */
+@RequiresApi(Build.VERSION_CODES.R)
 public class InfoMediaManager extends MediaManager {
 
     private static final String TAG = "InfoMediaManager";
@@ -145,9 +148,16 @@
     }
 
     private RoutingSessionInfo getRoutingSessionInfo() {
-        final List<RoutingSessionInfo> sessionInfos =
-                mRouterManager.getRoutingSessions(mPackageName);
+        return getRoutingSessionInfo(mPackageName);
+    }
 
+    private RoutingSessionInfo getRoutingSessionInfo(String packageName) {
+        final List<RoutingSessionInfo> sessionInfos =
+                mRouterManager.getRoutingSessions(packageName);
+
+        if (sessionInfos == null || sessionInfos.isEmpty()) {
+            return null;
+        }
         return sessionInfos.get(sessionInfos.size() - 1);
     }
 
@@ -367,33 +377,13 @@
     }
 
     boolean shouldDisableMediaOutput(String packageName) {
-        boolean shouldDisableMediaOutput = false;
         if (TextUtils.isEmpty(packageName)) {
             Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
-            return false;
+            return true;
         }
-        final List<MediaRoute2Info> infos = mRouterManager.getTransferableRoutes(packageName);
-        if (infos.size() == 1) {
-            final MediaRoute2Info info = infos.get(0);
-            final int deviceType = info.getType();
-            switch (deviceType) {
-                case TYPE_UNKNOWN:
-                case TYPE_REMOTE_TV:
-                case TYPE_REMOTE_SPEAKER:
-                case TYPE_GROUP:
-                    shouldDisableMediaOutput = true;
-                    break;
-                default:
-                    shouldDisableMediaOutput = false;
-                    break;
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "shouldDisableMediaOutput() MediaRoute2Info size : " + infos.size()
-                    + ", package name : " + packageName + ", shouldDisableMediaOutput : "
-                    + shouldDisableMediaOutput);
-        }
-        return shouldDisableMediaOutput;
+
+        // Disable when there is no transferable route
+        return mRouterManager.getTransferableRoutes(packageName).isEmpty();
     }
 
     @TargetApi(Build.VERSION_CODES.R)
@@ -456,7 +446,7 @@
     }
 
     private void buildAvailableRoutes() {
-        for (MediaRoute2Info route : mRouterManager.getTransferableRoutes(mPackageName)) {
+        for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) {
             if (DEBUG) {
                 Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
                         + route.getVolume() + ", type : " + route.getType());
@@ -465,6 +455,29 @@
         }
     }
 
+    private List<MediaRoute2Info> getAvailableRoutes(String packageName) {
+        final List<MediaRoute2Info> infos = new ArrayList<>();
+        RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
+        if (routingSessionInfo != null) {
+            infos.addAll(mRouterManager.getSelectedRoutes(routingSessionInfo));
+        }
+        final List<MediaRoute2Info> transferableRoutes =
+                mRouterManager.getTransferableRoutes(packageName);
+        for (MediaRoute2Info transferableRoute : transferableRoutes) {
+            boolean alreadyAdded = false;
+            for (MediaRoute2Info mediaRoute2Info : infos) {
+                if (TextUtils.equals(transferableRoute.getId(), mediaRoute2Info.getId())) {
+                    alreadyAdded = true;
+                    break;
+                }
+            }
+            if (!alreadyAdded) {
+                infos.add(transferableRoute);
+            }
+        }
+        return infos;
+    }
+
     @VisibleForTesting
     void addMediaDevice(MediaRoute2Info route) {
         final int deviceType = route.getType();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index a8da2c0..22001c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -22,11 +22,13 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.RoutingSessionInfo;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.A2dpProfile;
@@ -49,6 +51,7 @@
 /**
  * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
  */
+@RequiresApi(Build.VERSION_CODES.R)
 public class LocalMediaManager implements BluetoothCallback {
     private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
     private static final String TAG = "LocalMediaManager";
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
index d153bd8..ab17499 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
@@ -56,4 +56,16 @@
     default double getValue(int id, double def) {
         return def;
     }
+
+    /** Add a listener to be alerted when any flag changes. */
+    default void addListener(Listener listener) {}
+
+    /** Remove a listener to be alerted when any flag changes. */
+    default void removeListener(Listener listener) {}
+
+    /** A simple listener to be alerted when a flag changes. */
+    interface Listener {
+        /** */
+        void onFlagChanged(int id);
+    }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml
new file mode 100644
index 0000000..181ba07
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.
+-->
+<com.android.systemui.qs.QSFooterActionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/qs_footer_actions_container"
+    android:layout_width="match_parent"
+    android:layout_height="48dp"
+    android:gravity="center_vertical">
+
+    <com.android.systemui.statusbar.AlphaOptimizedImageView
+        android:id="@android:id/edit"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+        android:layout_weight="1"
+        android:background="@drawable/qs_footer_action_chip_background"
+        android:clickable="true"
+        android:clipToPadding="false"
+        android:contentDescription="@string/accessibility_quick_settings_edit"
+        android:focusable="true"
+        android:padding="@dimen/qs_footer_icon_padding"
+        android:src="@*android:drawable/ic_mode_edit"
+        android:tint="?android:attr/textColorPrimary" />
+
+    <com.android.systemui.statusbar.phone.MultiUserSwitch
+        android:id="@+id/multi_user_switch"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+        android:layout_weight="1"
+        android:background="@drawable/qs_footer_action_chip_background"
+        android:focusable="true">
+
+        <ImageView
+            android:id="@+id/multi_user_avatar"
+            android:layout_width="@dimen/multi_user_avatar_expanded_size"
+            android:layout_height="@dimen/multi_user_avatar_expanded_size"
+            android:layout_gravity="center"
+            android:scaleType="centerInside" />
+    </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+    <com.android.systemui.statusbar.AlphaOptimizedImageView
+        android:id="@+id/pm_lite"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+        android:layout_weight="1"
+        android:background="@drawable/qs_footer_action_chip_background"
+        android:clickable="true"
+        android:clipToPadding="false"
+        android:focusable="true"
+        android:padding="@dimen/qs_footer_icon_padding"
+        android:src="@*android:drawable/ic_lock_power_off"
+        android:contentDescription="@string/accessibility_quick_settings_power_menu"
+        android:tint="?android:attr/textColorPrimary" />
+
+    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+        android:id="@+id/settings_button_container"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:background="@drawable/qs_footer_action_chip_background"
+        android:layout_weight="1"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+
+        <com.android.systemui.statusbar.phone.SettingsButton
+            android:id="@+id/settings_button"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:layout_gravity="center"
+            android:contentDescription="@string/accessibility_quick_settings_settings"
+            android:background="@drawable/qs_footer_action_chip_background_borderless"
+            android:padding="@dimen/qs_footer_icon_padding"
+            android:scaleType="centerInside"
+            android:src="@drawable/ic_settings"
+            android:tint="?android:attr/textColorPrimary" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/tuner_icon"
+            android:layout_width="8dp"
+            android:layout_height="8dp"
+            android:layout_gravity="center_horizontal|bottom"
+            android:layout_marginBottom="@dimen/qs_footer_icon_padding"
+            android:src="@drawable/tuner"
+            android:tint="?android:attr/textColorTertiary"
+            android:visibility="invisible" />
+
+    </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+</com.android.systemui.qs.QSFooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/fp_to_unlock.xml b/packages/SystemUI/res/anim/fp_to_unlock.xml
index a348208..a5f75b6 100644
--- a/packages/SystemUI/res/anim/fp_to_unlock.xml
+++ b/packages/SystemUI/res/anim/fp_to_unlock.xml
@@ -19,10 +19,10 @@
       <group android:name="_R_G">
         <group android:name="_R_G_L_1_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
           <group android:name="_R_G_L_1_G" android:translateX="30.75" android:translateY="25.75">
-            <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
-            <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
-            <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
-            <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+            <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+            <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+            <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+            <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
           </group>
         </group>
         <group android:name="_R_G_L_0_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
diff --git a/packages/SystemUI/res/anim/lock_to_unlock.xml b/packages/SystemUI/res/anim/lock_to_unlock.xml
index ec51c01..76f7a05 100644
--- a/packages/SystemUI/res/anim/lock_to_unlock.xml
+++ b/packages/SystemUI/res/anim/lock_to_unlock.xml
@@ -21,7 +21,7 @@
           <group android:name="_R_G_L_2_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
             <group android:name="_R_G_L_2_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
               <group android:name="_R_G_L_2_G" android:translateX="-0.375" android:translateY="-22.375">
-                <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
+                <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
               </group>
             </group>
           </group>
@@ -30,7 +30,7 @@
           <group android:name="_R_G_L_1_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
             <group android:name="_R_G_L_1_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
               <group android:name="_R_G_L_1_G" android:translateX="5" android:translateY="-22.5">
-                <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
+                <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
               </group>
             </group>
           </group>
diff --git a/packages/SystemUI/res/drawable/ic_unlock.xml b/packages/SystemUI/res/drawable/ic_unlock.xml
index c3b3469..46023e6 100644
--- a/packages/SystemUI/res/drawable/ic_unlock.xml
+++ b/packages/SystemUI/res/drawable/ic_unlock.xml
@@ -21,7 +21,7 @@
             android:strokeColor="#FF000000"
             android:strokeLineCap="round"
             android:strokeLineJoin="round"
-            android:strokeWidth="2.5"
+            android:strokeWidth="2"
             android:pathData="M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
     </group>
     <group android:translateX="14" android:translateY="13.5">
@@ -29,7 +29,7 @@
             android:strokeColor="#FF000000"
             android:strokeLineCap="round"
             android:strokeLineJoin="round"
-            android:strokeWidth="2.5"
+            android:strokeWidth="2"
             android:pathData="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
     </group>
     <group android:translateX="20" android:translateY="35.75">
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 317dbc0..fe0b14a 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -68,93 +68,8 @@
 
         </LinearLayout>
 
-        <LinearLayout
-            android:id="@+id/qs_footer_actions_container"
-            android:layout_width="match_parent"
-            android:layout_height="48dp"
-            android:gravity="center_vertical">
+        <include layout="@layout/qs_footer_actions"/>
 
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@android:id/edit"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:clickable="true"
-                android:clipToPadding="false"
-                android:contentDescription="@string/accessibility_quick_settings_edit"
-                android:focusable="true"
-                android:padding="@dimen/qs_footer_icon_padding"
-                android:src="@*android:drawable/ic_mode_edit"
-                android:tint="?android:attr/textColorPrimary" />
-
-            <com.android.systemui.statusbar.phone.MultiUserSwitch
-                android:id="@+id/multi_user_switch"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:focusable="true">
-
-                <ImageView
-                    android:id="@+id/multi_user_avatar"
-                    android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                    android:layout_height="@dimen/multi_user_avatar_expanded_size"
-                    android:layout_gravity="center"
-                    android:scaleType="centerInside" />
-            </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/pm_lite"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-                android:layout_weight="1"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:clickable="true"
-                android:clipToPadding="false"
-                android:focusable="true"
-                android:padding="@dimen/qs_footer_icon_padding"
-                android:src="@*android:drawable/ic_lock_power_off"
-                android:contentDescription="@string/accessibility_quick_settings_power_menu"
-                android:tint="?android:attr/textColorPrimary" />
-
-            <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-                android:id="@+id/settings_button_container"
-                android:layout_width="0dp"
-                android:layout_height="@dimen/qs_footer_action_button_size"
-                android:background="@drawable/qs_footer_action_chip_background"
-                android:layout_weight="1"
-                android:clipChildren="false"
-                android:clipToPadding="false">
-
-                <com.android.systemui.statusbar.phone.SettingsButton
-                    android:id="@+id/settings_button"
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/qs_footer_action_button_size"
-                    android:layout_gravity="center"
-                    android:contentDescription="@string/accessibility_quick_settings_settings"
-                    android:background="@drawable/qs_footer_action_chip_background_borderless"
-                    android:padding="@dimen/qs_footer_icon_padding"
-                    android:scaleType="centerInside"
-                    android:src="@drawable/ic_settings"
-                    android:tint="?android:attr/textColorPrimary" />
-
-                <com.android.systemui.statusbar.AlphaOptimizedImageView
-                    android:id="@+id/tuner_icon"
-                    android:layout_width="8dp"
-                    android:layout_height="8dp"
-                    android:layout_gravity="center_horizontal|bottom"
-                    android:layout_marginBottom="@dimen/qs_footer_icon_padding"
-                    android:src="@drawable/tuner"
-                    android:tint="?android:attr/textColorTertiary"
-                    android:visibility="invisible" />
-
-            </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
-        </LinearLayout>
     </LinearLayout>
 
 </com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index e37a3f0..0c3c602 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -68,7 +68,7 @@
             android:id="@+id/lock_icon"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:padding="48px"
+            android:padding="@dimen/lock_icon_padding"
             android:layout_gravity="center"
             android:scaleType="centerCrop"/>
     </com.android.keyguard.LockIconView>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view.xml b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
index 18517906..a9eb27a 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
@@ -34,7 +34,7 @@
         android:id="@+id/udfps_aod_fp"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:padding="48px"
+        android:padding="@dimen/lock_icon_padding"
         android:layout_gravity="center"
         android:scaleType="centerCrop"
         app:lottie_autoPlay="false"
@@ -46,7 +46,7 @@
         android:id="@+id/udfps_lockscreen_fp"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:padding="48px"
+        android:padding="@dimen/lock_icon_padding"
         android:layout_gravity="center"
         android:scaleType="centerCrop"
         app:lottie_autoPlay="false"
diff --git a/packages/SystemUI/res/raw/udfps_aod_fp.json b/packages/SystemUI/res/raw/udfps_aod_fp.json
index 3247fe7..3b273ff 100644
--- a/packages/SystemUI/res/raw/udfps_aod_fp.json
+++ b/packages/SystemUI/res/raw/udfps_aod_fp.json
@@ -164,7 +164,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -526,7 +526,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -829,7 +829,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -1132,7 +1132,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -1507,7 +1507,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -1882,7 +1882,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -2257,7 +2257,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
@@ -2560,7 +2560,7 @@
               },
               "w":{
                 "a":0,
-                "k":1,
+                "k":1.3,
                 "ix":5
               },
               "lc":2,
diff --git a/packages/SystemUI/res/raw/udfps_lockscreen_fp.json b/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
index a25a475..a30a03a 100644
--- a/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
+++ b/packages/SystemUI/res/raw/udfps_lockscreen_fp.json
@@ -1 +1 @@
-{"v":"5.7.8","fr":60,"ip":0,"op":46,"w":46,"h":65,"nm":"fingerprint_build_on","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Fingerprint_20210701 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.091,32.5,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999994755,0.949000000954,0.624000012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2.5]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":210,"cm":"2","dr":0},{"tm":255,"cm":"1","dr":0}]}
\ No newline at end of file
+{"v":"5.7.8","fr":60,"ip":0,"op":46,"w":46,"h":65,"nm":"fingerprint_build_on","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Fingerprint_20210701 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[23.091,32.5,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999994755,0.949000000954,0.624000012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717999985639,0.948999980852,0.62400004069,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":24,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":210,"cm":"2","dr":0},{"tm":255,"cm":"1","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a12fd2b..28233f8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -538,6 +538,9 @@
     <!-- Gravity for the notification panel -->
     <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
 
+    <!-- Padding for the lock icon on the keyguard. In pixels - should not scale with display size. -->
+    <dimen name="lock_icon_padding">48px</dimen>
+
     <!-- Height of the carrier/wifi name label -->
     <dimen name="carrier_label_height">24dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 35001af..b9002c3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3014,6 +3014,8 @@
     <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
     <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
     <string name="tap_a_network_to_connect">Tap a network to connect</string>
+    <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+    <string name="unlock_to_view_networks">Unlock to view networks</string>
     <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
     <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
     <!-- Provider Model: Failure notification for connect -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 16f313f..6908409 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -347,13 +347,16 @@
     private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
     private static final int HAL_ERROR_RETRY_MAX = 20;
 
-    private final Runnable mCancelNotReceived = new Runnable() {
-        @Override
-        public void run() {
-            Log.w(TAG, "Cancel not received, transitioning to STOPPED");
-            mFingerprintRunningState = mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-            updateBiometricListeningState();
-        }
+    private final Runnable mFpCancelNotReceived = () -> {
+        Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
+        mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+        updateFingerprintListeningState();
+    };
+
+    private final Runnable mFaceCancelNotReceived = () -> {
+        Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+        updateFaceListeningState();
     };
 
     private final Handler mHandler;
@@ -791,19 +794,19 @@
 
     private void handleFingerprintError(int msgId, String errString) {
         Assert.isMainThread();
-        if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED && mHandler.hasCallbacks(
-                mCancelNotReceived)) {
-            mHandler.removeCallbacks(mCancelNotReceived);
+        if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
+            mHandler.removeCallbacks(mFpCancelNotReceived);
         }
 
+        // Error is always the end of authentication lifecycle.
+        mFingerprintCancelSignal = null;
+
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
                 && mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
             setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
             updateFingerprintListeningState();
         } else {
             setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
-            mFingerprintCancelSignal = null;
-            mFaceCancelSignal = null;
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
@@ -905,6 +908,7 @@
 
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
+        mFaceCancelSignal = null;
         setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -983,10 +987,13 @@
     private void handleFaceError(int msgId, String errString) {
         Assert.isMainThread();
         if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString);
-        if (msgId == FaceManager.FACE_ERROR_CANCELED && mHandler.hasCallbacks(mCancelNotReceived)) {
-            mHandler.removeCallbacks(mCancelNotReceived);
+        if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
+            mHandler.removeCallbacks(mFaceCancelNotReceived);
         }
 
+        // Error is always the end of authentication lifecycle
+        mFaceCancelSignal = null;
+
         if (msgId == FaceManager.FACE_ERROR_CANCELED
                 && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -2368,6 +2375,14 @@
     }
 
     private void startListeningForFingerprint() {
+        final int userId = getCurrentUser();
+        final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
+        if (mFingerprintCancelSignal != null) {
+            Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
+                    + " management. FP state: " + mFingerprintRunningState
+                    + ", unlockPossible: " + unlockPossible);
+        }
+
         if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
             setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
             return;
@@ -2377,11 +2392,8 @@
             return;
         }
         if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
-        int userId = getCurrentUser();
-        if (isUnlockWithFingerprintPossible(userId)) {
-            if (mFingerprintCancelSignal != null) {
-                mFingerprintCancelSignal.cancel();
-            }
+
+        if (unlockPossible) {
             mFingerprintCancelSignal = new CancellationSignal();
 
             if (isEncryptedOrLockdown(userId)) {
@@ -2397,6 +2409,14 @@
     }
 
     private void startListeningForFace() {
+        final int userId = getCurrentUser();
+        final boolean unlockPossible = isUnlockWithFacePossible(userId);
+        if (mFaceCancelSignal != null) {
+            Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
+                    + " management. Face state: " + mFaceRunningState
+                    + ", unlockPossible: " + unlockPossible);
+        }
+
         if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
             setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
             return;
@@ -2405,11 +2425,8 @@
             return;
         }
         if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
-        int userId = getCurrentUser();
-        if (isUnlockWithFacePossible(userId)) {
-            if (mFaceCancelSignal != null) {
-                mFaceCancelSignal.cancel();
-            }
+
+        if (unlockPossible) {
             mFaceCancelSignal = new CancellationSignal();
 
             // This would need to be updated for multi-sensor devices
@@ -2461,9 +2478,8 @@
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
                 mFingerprintCancelSignal = null;
-                if (!mHandler.hasCallbacks(mCancelNotReceived)) {
-                    mHandler.postDelayed(mCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
-                }
+                mHandler.removeCallbacks(mFpCancelNotReceived);
+                mHandler.postDelayed(mFpCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
             }
             setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
         }
@@ -2478,9 +2494,8 @@
             if (mFaceCancelSignal != null) {
                 mFaceCancelSignal.cancel();
                 mFaceCancelSignal = null;
-                if (!mHandler.hasCallbacks(mCancelNotReceived)) {
-                    mHandler.postDelayed(mCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
-                }
+                mHandler.removeCallbacks(mFaceCancelNotReceived);
+                mHandler.postDelayed(mFaceCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
             }
             setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 08247a8..d4d01c8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -114,6 +114,14 @@
         return mPlugin.getValue(flag.getId(), flag.getDefault());
     }
 
+    void addListener(FlagReaderPlugin.Listener listener) {
+        mPlugin.addListener(listener);
+    }
+
+    void removeListener(FlagReaderPlugin.Listener listener) {
+        mPlugin.removeListener(listener);
+    }
+
     /**
      * Returns true if the specified feature flag has been enabled.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 0c9e6de..e51f90f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -19,11 +19,14 @@
 import android.content.Context;
 import android.util.FeatureFlagUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.FlagReaderPlugin;
 
-import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.inject.Inject;
@@ -37,11 +40,26 @@
 public class FeatureFlags {
     private final FeatureFlagReader mFlagReader;
     private final Context mContext;
+    private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
+    private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
 
     @Inject
     public FeatureFlags(FeatureFlagReader flagReader, Context context) {
         mFlagReader = flagReader;
         mContext = context;
+
+        flagReader.addListener(mListener);
+    }
+
+    private final FlagReaderPlugin.Listener mListener = id -> {
+        if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
+            mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
+        }
+    };
+
+    @VisibleForTesting
+    void addFlag(Flag flag) {
+        mFlagMap.put(flag.getId(), flag);
     }
 
     /**
@@ -92,6 +110,20 @@
         return mFlagReader.getValue(flag);
     }
 
+    /** Add a listener for a specific flag. */
+    public void addFlagListener(Flag<?> flag, Listener listener) {
+        mListeners.putIfAbsent(flag.getId(), new ArrayList<>());
+        mListeners.get(flag.getId()).add(listener);
+        mFlagMap.putIfAbsent(flag.getId(), flag);
+    }
+
+    /** Remove a listener for a specific flag. */
+    public void removeFlagListener(Flag<?> flag, Listener listener) {
+        if (mListeners.containsKey(flag.getId())) {
+            mListeners.get(flag.getId()).remove(listener);
+        }
+    }
+
     public boolean isNewNotifPipelineEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
     }
@@ -160,27 +192,6 @@
         return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
 
-    private Map<Integer, Flag<?>> collectFlags() {
-        Map<Integer, Flag<?>> flags = new HashMap<>();
-
-        Field[] fields = this.getClass().getFields();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                try {
-                    //flags.add((Flag<?>) field.get(null));
-                    Flag flag = (Flag) field.get(null);
-                    flags.put(flag.getId(), flag);
-                } catch (IllegalAccessException e) {
-                    // no-op
-                }
-            }
-        }
-
-        return flags;
-    }
-
     /** Simple interface for beinga alerted when a specific flag changes value. */
     public interface Listener {
         /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 85f2366..2817718 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2603,7 +2603,7 @@
             if (mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_guestUserAutoCreated)) {
                 // TODO(b/191067027): Move post-boot guest creation to system_server
-                mUserSwitcherController.guaranteeGuestPresent();
+                mUserSwitcherController.schedulePostBootGuestCreation();
             }
             mBootCompleted = true;
             adjustStatusBarLocked(false, true);
@@ -2759,7 +2759,10 @@
 
         // Don't hide the keyguard due to a doze change if there's a lock pending, because we're
         // just going to show it again.
-        if (mShowing || !mPendingLock) {
+        // If the device is not capable of controlling the screen off animation, SysUI needs to
+        // update lock screen state in ATMS here, otherwise ATMS tries to resume activities when
+        // enabling doze state.
+        if (mShowing || !mPendingLock || !mDozeParameters.canControlUnlockedScreenOff()) {
             setShowingLocked(mShowing);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 543004e..1ca2217 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -116,7 +116,7 @@
     private final TaskbarDelegate mTaskbarDelegate;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
     private int mNavMode;
-    private boolean mIsTablet;
+    @VisibleForTesting boolean mIsTablet;
     private final UserTracker mUserTracker;
 
     /** A displayId - nav bar maps. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index e38bd4b..0e0681b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -35,11 +35,6 @@
     void setExpanded(boolean expanded);
 
     /**
-     * Returns the full height of the footer.
-     */
-    int getHeight();
-
-    /**
      * Sets the percentage amount that the quick settings has been expanded.
      *
      * @param expansion A value from 1 to 0 that indicates how much the quick settings have been
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt
new file mode 100644
index 0000000..dbf62a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.qs
+
+import android.content.Intent
+import android.os.UserManager
+import android.provider.Settings
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.phone.SettingsButton
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.ViewController
+import javax.inject.Inject
+import javax.inject.Named
+
+class QSFooterActionsController @Inject constructor(
+    view: QSFooterActionsView,
+    private val qsPanelController: QSPanelController,
+    private val activityStarter: ActivityStarter,
+    private val userManager: UserManager,
+    private val userInfoController: UserInfoController,
+    private val multiUserSwitchController: MultiUserSwitchController,
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val falsingManager: FalsingManager,
+    private val metricsLogger: MetricsLogger,
+    private val tunerService: TunerService,
+    private val globalActionsDialog: GlobalActionsDialogLite,
+    private val uiEventLogger: UiEventLogger,
+    @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+) : ViewController<QSFooterActionsView>(view) {
+
+    private var listening: Boolean = false
+    var expanded = false
+        set(value) {
+            field = value
+            mView.setExpanded(value, isTunerEnabled(),
+                    multiUserSwitchController.isMultiUserEnabled)
+        }
+
+    private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
+    private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+    private val editButton: View = view.findViewById(android.R.id.edit)
+    private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
+
+    private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
+        val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+        mView.onUserInfoChanged(picture, isGuestUser)
+    }
+
+    private val onClickListener = View.OnClickListener { v ->
+        // Don't do anything until views are unhidden. Don't do anything if the tap looks
+        // suspicious.
+        if (!expanded || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return@OnClickListener
+        }
+        if (v === settingsButton) {
+            if (!deviceProvisionedController.isCurrentUserSetup) {
+                // If user isn't setup just unlock the device and dump them back at SUW.
+                activityStarter.postQSRunnableDismissingKeyguard {}
+                return@OnClickListener
+            }
+            metricsLogger.action(
+                    if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
+                    else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
+            if (settingsButton.isTunerClick) {
+                activityStarter.postQSRunnableDismissingKeyguard {
+                    if (isTunerEnabled()) {
+                        tunerService.showResetRequest {
+                            // Relaunch settings so that the tuner disappears.
+                            startSettingsActivity()
+                        }
+                    } else {
+                        Toast.makeText(context, R.string.tuner_toast, Toast.LENGTH_LONG).show()
+                        tunerService.isTunerEnabled = true
+                    }
+                    startSettingsActivity()
+                }
+            } else {
+                startSettingsActivity()
+            }
+        } else if (v === powerMenuLite) {
+            uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+            globalActionsDialog.showOrHideDialog(false, true)
+        }
+    }
+
+    override fun onInit() {
+        multiUserSwitchController.init()
+    }
+
+    private fun startSettingsActivity() {
+        val animationController = settingsButtonContainer?.let {
+            ActivityLaunchAnimator.Controller.fromView(
+                    it,
+                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
+            }
+        activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
+                true /* dismissShade */, animationController)
+    }
+
+    @VisibleForTesting
+    public override fun onViewAttached() {
+        if (showPMLiteButton) {
+            powerMenuLite.visibility = View.VISIBLE
+            powerMenuLite.setOnClickListener(onClickListener)
+        } else {
+            powerMenuLite.visibility = View.GONE
+        }
+        settingsButton.setOnClickListener(onClickListener)
+        editButton.setOnClickListener(View.OnClickListener { view: View? ->
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                return@OnClickListener
+            }
+            activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) }
+        })
+
+        mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+    }
+
+    override fun onViewDetached() {
+        setListening(false)
+    }
+
+    fun setListening(listening: Boolean) {
+        if (this.listening == listening) {
+            return
+        }
+        this.listening = listening
+        if (this.listening) {
+            userInfoController.addCallback(onUserInfoChangedListener)
+        } else {
+            userInfoController.removeCallback(onUserInfoChangedListener)
+        }
+    }
+
+    fun disable(state2: Int) {
+        mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+    }
+
+    fun setExpansion(headerExpansionFraction: Float) {
+        mView.setExpansion(headerExpansionFraction)
+    }
+
+    fun updateAnimator(width: Int, numTiles: Int) {
+        mView.updateAnimator(width, numTiles)
+    }
+
+    fun setKeyguardShowing() {
+        mView.setKeyguardShowing()
+    }
+
+    private fun isTunerEnabled() = tunerService.isTunerEnabled
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt
new file mode 100644
index 0000000..66a29a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.qs
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.RippleDrawable
+import android.os.UserManager
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.settingslib.Utils
+import com.android.settingslib.drawable.UserIconDrawable
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.MultiUserSwitch
+import com.android.systemui.statusbar.phone.SettingsButton
+
+/**
+ * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
+ * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
+ * edit tiles, power off and conditionally: user switch and tuner
+ */
+class QSFooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+    private lateinit var settingsContainer: View
+    private lateinit var settingsButton: SettingsButton
+    private lateinit var multiUserSwitch: MultiUserSwitch
+    private lateinit var multiUserAvatar: ImageView
+    private lateinit var tunerIcon: View
+    private lateinit var editTilesButton: View
+
+    private var settingsCogAnimator: TouchAnimator? = null
+
+    private var qsDisabled = false
+    private var isExpanded = false
+    private var expansionAmount = 0f
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        editTilesButton = requireViewById(android.R.id.edit)
+        settingsButton = findViewById(R.id.settings_button)
+        settingsContainer = findViewById(R.id.settings_button_container)
+        multiUserSwitch = findViewById(R.id.multi_user_switch)
+        multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
+        tunerIcon = requireViewById(R.id.tuner_icon)
+
+        // RenderThread is doing more harm than good when touching the header (to expand quick
+        // settings), so disable it for this view
+        if (settingsButton.background is RippleDrawable) {
+            (settingsButton.background as RippleDrawable).setForceSoftware(true)
+        }
+        updateResources()
+        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+    }
+
+    fun updateAnimator(width: Int, numTiles: Int) {
+        val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
+                mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
+        val remaining = (width - numTiles * size) / (numTiles - 1)
+        val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
+        val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
+        settingsCogAnimator = TouchAnimator.Builder()
+                .addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
+                .addFloat(settingsButton, "rotation", -120f, 0f)
+                .build()
+        setExpansion(expansionAmount)
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    override fun onRtlPropertiesChanged(layoutDirection: Int) {
+        super.onRtlPropertiesChanged(layoutDirection)
+        updateResources()
+    }
+
+    private fun updateResources() {
+        val tunerIconTranslation = mContext.resources
+                .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation).toFloat()
+        tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
+    }
+
+    fun setKeyguardShowing() {
+        setExpansion(expansionAmount)
+    }
+
+    fun setExpanded(expanded: Boolean, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+        if (isExpanded == expanded) return
+        isExpanded = expanded
+        updateEverything(isTunerEnabled, multiUserEnabled)
+    }
+
+    fun setExpansion(headerExpansionFraction: Float) {
+        expansionAmount = headerExpansionFraction
+        if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
+    }
+
+    fun disable(state2: Int, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+        val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+        if (disabled == qsDisabled) return
+        qsDisabled = disabled
+        updateEverything(isTunerEnabled, multiUserEnabled)
+    }
+
+    fun updateEverything(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+        post {
+            updateVisibilities(isTunerEnabled, multiUserEnabled)
+            updateClickabilities()
+            isClickable = false
+        }
+    }
+
+    private fun updateClickabilities() {
+        multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
+        editTilesButton.isClickable = editTilesButton.visibility == VISIBLE
+        settingsButton.isClickable = settingsButton.visibility == VISIBLE
+    }
+
+    private fun updateVisibilities(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
+        settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
+        tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE
+        multiUserSwitch.visibility = if (showUserSwitcher(multiUserEnabled)) VISIBLE else GONE
+        val isDemo = UserManager.isDeviceInDemoMode(context)
+        settingsButton.visibility = if (isDemo || !isExpanded) INVISIBLE else VISIBLE
+    }
+
+    private fun showUserSwitcher(multiUserEnabled: Boolean): Boolean {
+        return isExpanded && multiUserEnabled
+    }
+
+    fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
+        var pictureToSet = picture
+        if (picture != null && isGuestUser && picture !is UserIconDrawable) {
+            pictureToSet = picture.constantState.newDrawable(resources).mutate()
+            pictureToSet.setColorFilter(
+                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
+                    PorterDuff.Mode.SRC_IN)
+        }
+        multiUserAvatar.setImageDrawable(pictureToSet)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 57438d1..7db13bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -21,60 +21,40 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settingslib.Utils;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
-import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
-import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
-import com.android.systemui.statusbar.phone.SettingsButton;
 
-/** */
+/**
+ * Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
+ * {@link QSFooterActionsView}
+ */
 public class QSFooterView extends FrameLayout {
-    private SettingsButton mSettingsButton;
-    protected View mSettingsContainer;
     private PageIndicator mPageIndicator;
     private TextView mBuildText;
-    private boolean mShouldShowBuildText;
-
-    private boolean mQsDisabled;
-
-    private boolean mExpanded;
-
-    private boolean mListening;
-
-    protected MultiUserSwitch mMultiUserSwitch;
-    private ImageView mMultiUserAvatar;
+    private View mActionsContainer;
 
     protected TouchAnimator mFooterAnimator;
+
+    private boolean mQsDisabled;
+    private boolean mExpanded;
     private float mExpansionAmount;
 
-    protected View mEdit;
-    private TouchAnimator mSettingsCogAnimator;
-
-    private View mActionsContainer;
-    private View mTunerIcon;
-    private int mTunerIconTranslation;
+    private boolean mShouldShowBuildText;
 
     private OnClickListener mExpandClickListener;
 
@@ -94,27 +74,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mEdit = requireViewById(android.R.id.edit);
-
         mPageIndicator = findViewById(R.id.footer_page_indicator);
-
-        mSettingsButton = findViewById(R.id.settings_button);
-        mSettingsContainer = findViewById(R.id.settings_button_container);
-
-        mMultiUserSwitch = findViewById(R.id.multi_user_switch);
-        mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
-
         mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
         mBuildText = findViewById(R.id.build);
-        mTunerIcon = requireViewById(R.id.tuner_icon);
 
-        // RenderThread is doing more harm than good when touching the header (to expand quick
-        // settings), so disable it for this view
-        if (mSettingsButton.getBackground() instanceof RippleDrawable) {
-            ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
-        }
         updateResources();
-
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         setBuildText();
     }
@@ -137,18 +101,7 @@
         }
     }
 
-    void updateAnimator(int width, int numTiles) {
-        int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
-                - mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
-        int remaining = (width - numTiles * size) / (numTiles - 1);
-        int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
-
-        mSettingsCogAnimator = new Builder()
-                .addFloat(mSettingsButton, "translationX",
-                        isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
-                .addFloat(mSettingsButton, "rotation", -120, 0)
-                .build();
-
+    void updateExpansion() {
         setExpansion(mExpansionAmount);
     }
 
@@ -158,20 +111,11 @@
         updateResources();
     }
 
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        super.onRtlPropertiesChanged(layoutDirection);
-        updateResources();
-    }
-
     private void updateResources() {
         updateFooterAnimator();
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
         lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
         setLayoutParams(lp);
-        mTunerIconTranslation = mContext.getResources()
-                .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation);
-        mTunerIcon.setTranslationX(isLayoutRtl() ? -mTunerIconTranslation : mTunerIconTranslation);
     }
 
     private void updateFooterAnimator() {
@@ -197,17 +141,15 @@
         mExpandClickListener = onClickListener;
     }
 
-    void setExpanded(boolean expanded, boolean isTunerEnabled, boolean multiUserEnabled) {
+    void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
-        updateEverything(isTunerEnabled, multiUserEnabled);
+        updateEverything();
     }
 
     /** */
     public void setExpansion(float headerExpansionFraction) {
         mExpansionAmount = headerExpansionFraction;
-        if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
-
         if (mFooterAnimator != null) {
             mFooterAnimator.setPosition(headerExpansionFraction);
         }
@@ -228,14 +170,6 @@
         super.onDetachedFromWindow();
     }
 
-    /** */
-    public void setListening(boolean listening) {
-        if (listening == mListening) {
-            return;
-        }
-        mListening = listening;
-    }
-
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
         if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
@@ -253,50 +187,26 @@
         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
     }
 
-    void disable(int state2, boolean isTunerEnabled, boolean multiUserEnabled) {
+    void disable(int state2) {
         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
         if (disabled == mQsDisabled) return;
         mQsDisabled = disabled;
-        updateEverything(isTunerEnabled, multiUserEnabled);
+        updateEverything();
     }
 
-    void updateEverything(boolean isTunerEnabled, boolean multiUserEnabled) {
+    void updateEverything() {
         post(() -> {
-            updateVisibilities(isTunerEnabled, multiUserEnabled);
+            updateVisibilities();
             updateClickabilities();
             setClickable(false);
         });
     }
 
     private void updateClickabilities() {
-        mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
-        mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
-        mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
         mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
     }
 
-    private void updateVisibilities(boolean isTunerEnabled, boolean multiUserEnabled) {
-        mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
-        mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
-        final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
-        mMultiUserSwitch.setVisibility(
-                showUserSwitcher(multiUserEnabled) ? View.VISIBLE : View.GONE);
-        mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
-
+    private void updateVisibilities() {
         mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
     }
-
-    private boolean showUserSwitcher(boolean multiUserEnabled) {
-        return mExpanded && multiUserEnabled;
-    }
-
-    void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
-        if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
-            picture = picture.getConstantState().newDrawable(getResources()).mutate();
-            picture.setColorFilter(
-                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
-                    Mode.SRC_IN);
-        }
-        mMultiUserAvatar.setImageDrawable(picture);
-    }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 929aeda..c8ae590 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,39 +16,19 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED;
-
 import android.content.ClipData;
 import android.content.ClipboardManager;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * Controller for {@link QSFooterView}.
@@ -56,137 +36,45 @@
 @QSScope
 public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
 
-    private final UserManager mUserManager;
-    private final UserInfoController mUserInfoController;
-    private final ActivityStarter mActivityStarter;
-    private final DeviceProvisionedController mDeviceProvisionedController;
     private final UserTracker mUserTracker;
     private final QSPanelController mQsPanelController;
     private final QuickQSPanelController mQuickQSPanelController;
-    private final TunerService mTunerService;
-    private final MetricsLogger mMetricsLogger;
-    private final FalsingManager mFalsingManager;
-    private final MultiUserSwitchController mMultiUserSwitchController;
-    private final SettingsButton mSettingsButton;
-    private final View mSettingsButtonContainer;
+    private final QSFooterActionsController mQsFooterActionsController;
     private final TextView mBuildText;
-    private final View mEdit;
     private final PageIndicator mPageIndicator;
-    private final View mPowerMenuLite;
-    private final boolean mShowPMLiteButton;
-    private final GlobalActionsDialogLite mGlobalActionsDialog;
-    private final UiEventLogger mUiEventLogger;
-
-    private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
-            new UserInfoController.OnUserInfoChangedListener() {
-        @Override
-        public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
-            boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
-            mView.onUserInfoChanged(picture, isGuestUser);
-        }
-    };
-
-    private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            // Don't do anything until views are unhidden. Don't do anything if the tap looks
-            // suspicious.
-            if (!mExpanded || mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return;
-            }
-
-            if (v == mSettingsButton) {
-                if (!mDeviceProvisionedController.isCurrentUserSetup()) {
-                    // If user isn't setup just unlock the device and dump them back at SUW.
-                    mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                    });
-                    return;
-                }
-                mMetricsLogger.action(
-                        mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
-                                : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
-                if (mSettingsButton.isTunerClick()) {
-                    mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                        if (isTunerEnabled()) {
-                            mTunerService.showResetRequest(
-                                    () -> {
-                                        // Relaunch settings so that the tuner disappears.
-                                        startSettingsActivity();
-                                    });
-                        } else {
-                            Toast.makeText(getContext(), R.string.tuner_toast,
-                                    Toast.LENGTH_LONG).show();
-                            mTunerService.setTunerEnabled(true);
-                        }
-                        startSettingsActivity();
-
-                    });
-                } else {
-                    startSettingsActivity();
-                }
-            } else if (v == mPowerMenuLite) {
-                mUiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
-                mGlobalActionsDialog.showOrHideDialog(false, true);
-            }
-        }
-    };
-
-    private boolean mListening;
-    private boolean mExpanded;
 
     @Inject
-    QSFooterViewController(QSFooterView view, UserManager userManager,
-            UserInfoController userInfoController, ActivityStarter activityStarter,
-            DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
+    QSFooterViewController(QSFooterView view,
+            UserTracker userTracker,
             QSPanelController qsPanelController,
-            MultiUserSwitchController multiUserSwitchController,
             QuickQSPanelController quickQSPanelController,
-            TunerService tunerService, MetricsLogger metricsLogger, FalsingManager falsingManager,
-            @Named(PM_LITE_ENABLED) boolean showPMLiteButton,
-            GlobalActionsDialogLite globalActionsDialog, UiEventLogger uiEventLogger) {
+            QSFooterActionsController qsFooterActionsController) {
         super(view);
-        mUserManager = userManager;
-        mUserInfoController = userInfoController;
-        mActivityStarter = activityStarter;
-        mDeviceProvisionedController = deviceProvisionedController;
         mUserTracker = userTracker;
         mQsPanelController = qsPanelController;
         mQuickQSPanelController = quickQSPanelController;
-        mTunerService = tunerService;
-        mMetricsLogger = metricsLogger;
-        mFalsingManager = falsingManager;
-        mMultiUserSwitchController = multiUserSwitchController;
+        mQsFooterActionsController = qsFooterActionsController;
 
-        mSettingsButton = mView.findViewById(R.id.settings_button);
-        mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container);
         mBuildText = mView.findViewById(R.id.build);
-        mEdit = mView.findViewById(android.R.id.edit);
         mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
-        mPowerMenuLite = mView.findViewById(R.id.pm_lite);
-        mShowPMLiteButton = showPMLiteButton;
-        mGlobalActionsDialog = globalActionsDialog;
-        mUiEventLogger = uiEventLogger;
     }
 
     @Override
     protected void onInit() {
         super.onInit();
-        mMultiUserSwitchController.init();
+        mQsFooterActionsController.init();
     }
 
     @Override
     protected void onViewAttached() {
-        if (mShowPMLiteButton) {
-            mPowerMenuLite.setVisibility(View.VISIBLE);
-            mPowerMenuLite.setOnClickListener(mSettingsOnClickListener);
-        } else {
-            mPowerMenuLite.setVisibility(View.GONE);
-        }
         mView.addOnLayoutChangeListener(
-                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                        mView.updateAnimator(
-                                right - left, mQuickQSPanelController.getNumQuickTiles()));
-        mSettingsButton.setOnClickListener(mSettingsOnClickListener);
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    mView.updateExpansion();
+                    mQsFooterActionsController.updateAnimator(right - left,
+                            mQuickQSPanelController.getNumQuickTiles());
+                }
+        );
+
         mBuildText.setOnLongClickListener(view -> {
             CharSequence buildText = mBuildText.getText();
             if (!TextUtils.isEmpty(buildText)) {
@@ -200,17 +88,8 @@
             }
             return false;
         });
-
-        mEdit.setOnClickListener(view -> {
-            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return;
-            }
-            mActivityStarter.postQSRunnableDismissingKeyguard(() ->
-                    mQsPanelController.showEdit(view));
-        });
-
         mQsPanelController.setFooterPageIndicator(mPageIndicator);
-        mView.updateEverything(isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
+        mView.updateEverything();
     }
 
     @Override
@@ -225,38 +104,25 @@
 
     @Override
     public void setExpanded(boolean expanded) {
-        mExpanded = expanded;
-        mView.setExpanded(
-                expanded, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
-    }
-
-    @Override
-    public int getHeight() {
-        return mView.getHeight();
+        mQsFooterActionsController.setExpanded(expanded);
+        mView.setExpanded(expanded);
     }
 
     @Override
     public void setExpansion(float expansion) {
         mView.setExpansion(expansion);
+        mQsFooterActionsController.setExpansion(expansion);
     }
 
     @Override
     public void setListening(boolean listening) {
-        if (mListening == listening) {
-            return;
-        }
-
-        mListening = listening;
-        if (mListening) {
-            mUserInfoController.addCallback(mOnUserInfoChangedListener);
-        } else {
-            mUserInfoController.removeCallback(mOnUserInfoChangedListener);
-        }
+        mQsFooterActionsController.setListening(listening);
     }
 
     @Override
     public void setKeyguardShowing(boolean keyguardShowing) {
         mView.setKeyguardShowing();
+        mQsFooterActionsController.setKeyguardShowing();
     }
 
     /** */
@@ -267,19 +133,7 @@
 
     @Override
     public void disable(int state1, int state2, boolean animate) {
-        mView.disable(state2, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
-    }
-
-    private void startSettingsActivity() {
-        ActivityLaunchAnimator.Controller animationController =
-                mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
-                        mSettingsButtonContainer,
-                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null;
-        mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
-                true /* dismissShade */, animationController);
-    }
-
-    private boolean isTunerEnabled() {
-        return mTunerService.isTunerEnabled();
+        mView.disable(state2);
+        mQsFooterActionsController.disable(state2);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 2046550..2de2d04 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -28,6 +28,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSContainerImpl;
 import com.android.systemui.qs.QSFooter;
+import com.android.systemui.qs.QSFooterActionsView;
 import com.android.systemui.qs.QSFooterView;
 import com.android.systemui.qs.QSFooterViewController;
 import com.android.systemui.qs.QSFragment;
@@ -122,6 +123,12 @@
 
     /** */
     @Provides
+    static QSFooterActionsView providesQSFooterActionsView(@RootView View view) {
+        return view.findViewById(R.id.qs_footer_actions_container);
+    }
+
+    /** */
+    @Provides
     @QSScope
     static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
         qsFooterViewController.init();
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 a63d1f8..e338750 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
@@ -287,14 +287,22 @@
         }
         showProgressBar();
         setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular());
-        setConnectedWifiLayout();
-        boolean isWifiEnabled = mWifiManager.isWifiEnabled();
-        mWiFiToggle.setChecked(isWifiEnabled);
-        int visible = isWifiEnabled ? View.VISIBLE : View.GONE;
-        mWifiRecyclerView.setVisibility(visible);
-        mAdapter.notifyDataSetChanged();
-        mSeeAllLayout.setVisibility(visible);
-        mSpace.setVisibility(isWifiEnabled ? View.GONE : View.VISIBLE);
+
+        final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+        final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+        updateWifiToggle(isWifiEnabled, isDeviceLocked);
+        updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+
+        List<WifiEntry> wifiEntryList = mInternetDialogController.getWifiEntryList();
+        final int wifiListVisibility =
+                (isDeviceLocked || wifiEntryList == null || wifiEntryList.size() <= 0)
+                        ? View.GONE : View.VISIBLE;
+        mWifiRecyclerView.setVisibility(wifiListVisibility);
+        if (wifiListVisibility == View.VISIBLE) {
+            mAdapter.notifyDataSetChanged();
+        }
+        mSeeAllLayout.setVisibility(wifiListVisibility);
+        mSpace.setVisibility(wifiListVisibility == View.VISIBLE ? View.GONE : View.VISIBLE);
     }
 
     private void setOnClickListener() {
@@ -358,8 +366,14 @@
         }
     }
 
-    private void setConnectedWifiLayout() {
-        if (!mWifiManager.isWifiEnabled() || mConnectedWifiEntry == null) {
+    private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+        mWiFiToggle.setChecked(isWifiEnabled);
+        mTurnWifiOnLayout.setBackground(
+                (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+    }
+
+    private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+        if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
             mConnectedWifListLayout.setBackground(null);
             mConnectedWifListLayout.setVisibility(View.GONE);
             return;
@@ -418,7 +432,8 @@
     }
 
     protected void showProgressBar() {
-        if (mWifiManager == null || !mWifiManager.isWifiEnabled()) {
+        if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+                || mInternetDialogController.isDeviceLocked()) {
             setProgressBarVisible(false);
             return;
         }
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 890dcfd..80a93d6 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
@@ -67,6 +67,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -97,6 +98,8 @@
     private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
     private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
             R.string.tap_a_network_to_connect;
+    private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+            R.string.unlock_to_view_networks;
     private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
             R.string.wifi_empty_list_wifi_on;
     private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
@@ -137,6 +140,9 @@
     @VisibleForTesting
     protected WifiUtils.InternetIconInjector mWifiIconInjector;
 
+    @VisibleForTesting
+    KeyguardStateController mKeyguardStateController;
+
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -161,7 +167,7 @@
             @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
             @Main Handler handler, @Main Executor mainExecutor,
             BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
-            GlobalSettings globalSettings) {
+            GlobalSettings globalSettings, KeyguardStateController keyguardStateController) {
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialogController");
         }
@@ -175,6 +181,7 @@
         mSubscriptionManager = subscriptionManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStateController = keyguardStateController;
         mConnectionStateFilter = new IntentFilter();
         mConnectionStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         mConnectionStateFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
@@ -272,6 +279,15 @@
             return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
         }
 
+        if (isDeviceLocked()) {
+            // When the device is locked.
+            //   Sub-Title: Unlock to view networks
+            if (DEBUG) {
+                Log.d(TAG, "The device is locked.");
+            }
+            return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+        }
+
         final List<ScanResult> wifiList = mWifiManager.getScanResults();
         if (wifiList != null && wifiList.size() != 0) {
             return mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT);
@@ -683,6 +699,10 @@
                 && serviceState.getState() == serviceState.STATE_IN_SERVICE;
     }
 
+    public boolean isDeviceLocked() {
+        return !mKeyguardStateController.isUnlocked();
+    }
+
     boolean activeNetworkIsCellular() {
         if (mConnectivityManager == null) {
             if (DEBUG) {
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 8f1a578..7548d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -26,6 +26,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityTaskManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -114,6 +115,8 @@
     @VisibleForTesting
     final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
     private final KeyguardStateController mKeyguardStateController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final DevicePolicyManager mDevicePolicyManager;
     protected final Handler mHandler;
     private final ActivityStarter mActivityStarter;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -149,6 +152,8 @@
             UserManager userManager,
             UserTracker userTracker,
             KeyguardStateController keyguardStateController,
+            DeviceProvisionedController deviceProvisionedController,
+            DevicePolicyManager devicePolicyManager,
             @Main Handler handler,
             ActivityStarter activityStarter,
             BroadcastDispatcher broadcastDispatcher,
@@ -178,6 +183,8 @@
         mGuestIsResetting = new AtomicBoolean();
         mGuestCreationScheduled = new AtomicBoolean();
         mKeyguardStateController = keyguardStateController;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mDevicePolicyManager = devicePolicyManager;
         mHandler = handler;
         mActivityStarter = activityStarter;
         mUserManager = userManager;
@@ -733,10 +740,27 @@
     }
 
     /**
+     * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
+     * observer to wait until the device is provisioned, then schedule the guest creation.
+     */
+    public void schedulePostBootGuestCreation() {
+        if (isDeviceAllowedToAddGuest()) {
+            guaranteeGuestPresent();
+        } else {
+            mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
+        }
+    }
+
+    private boolean isDeviceAllowedToAddGuest() {
+        return mDeviceProvisionedController.isDeviceProvisioned()
+                && !mDevicePolicyManager.isDeviceManaged();
+    }
+
+    /**
      * If there is no guest on the device, schedule creation of a new guest user in the background.
      */
-    public void guaranteeGuestPresent() {
-        if (mUserManager.findCurrentGuestUser() == null) {
+    private void guaranteeGuestPresent() {
+        if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
             scheduleGuestCreation();
         }
     }
@@ -1056,6 +1080,21 @@
                 }
             };
 
+    private final DeviceProvisionedController.DeviceProvisionedListener
+            mGuaranteeGuestPresentAfterProvisioned =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    if (isDeviceAllowedToAddGuest()) {
+                        mBgExecutor.execute(
+                                () -> mDeviceProvisionedController.removeCallback(
+                                        mGuaranteeGuestPresentAfterProvisioned));
+                        guaranteeGuestPresent();
+                    }
+                }
+            };
+
+
     private final class ExitGuestDialog extends SystemUIDialog implements
             DialogInterface.OnClickListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index f317628..dbf115b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2255,6 +2255,11 @@
 
         @Override
         public void onClick(View view) {
+            // If the ringer drawer isn't open, don't let anything in it be clicked.
+            if (!mIsRingerDrawerOpen) {
+                return;
+            }
+
             setRingerMode(mClickedRingerMode);
 
             mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 7e733a9..7e3553a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -213,6 +214,13 @@
     }
 
     //
+    // Freeform (optional feature)
+    //
+
+    @BindsOptionalOf
+    abstract Optional<FreeformTaskListener> optionalFreeformTaskListener();
+
+    //
     // Hide display cutout
     //
 
@@ -328,9 +336,11 @@
     @WMSingleton
     @Provides
     static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            Context context, @ShellMainThread ShellExecutor mainExecutor,
+            DisplayController displayController, Context context,
+            @ShellMainThread ShellExecutor mainExecutor,
             @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
+        return new Transitions(organizer, pool, displayController, context, mainExecutor,
+                animExecutor);
     }
 
     //
@@ -451,6 +461,7 @@
             Optional<AppPairsController> appPairsOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
+            Optional<Optional<FreeformTaskListener>> freeformTaskListener,
             Transitions transitions,
             StartingWindowController startingWindow,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -463,6 +474,7 @@
                 appPairsOptional,
                 pipTouchHandlerOptional,
                 fullscreenTaskListener,
+                freeformTaskListener,
                 transitions,
                 startingWindow,
                 mainExecutor);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index be7813e..6397ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -89,6 +90,18 @@
     }
 
     //
+    // Freeform
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<FreeformTaskListener> provideFreeformTaskListener(
+            Context context,
+            SyncTransactionQueue syncQueue) {
+        return Optional.ofNullable(FreeformTaskListener.create(context, syncQueue));
+    }
+
+    //
     // Split/multiwindow
     //
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..1a96178
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FlagReaderPlugin;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagsTest extends SysuiTestCase {
+
+    @Mock FeatureFlagReader mFeatureFlagReader;
+
+    private FeatureFlags mFeatureFlags;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFeatureFlags = new FeatureFlags(mFeatureFlagReader, getContext());
+    }
+
+    @Test
+    public void testAddListener() {
+        Flag<?> flag = new BooleanFlag(1);
+        mFeatureFlags.addFlag(flag);
+
+        // Assert and capture that a plugin listener was added.
+        ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+                ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+        FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+        // Signal a change. No listeners, so no real effect.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Add a listener for the flag
+        final Flag<?>[] changedFlag = {null};
+        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+        mFeatureFlags.addFlagListener(flag, listener);
+
+        // No changes seen yet.
+        assertThat(changedFlag[0]).isNull();
+
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Assert that the change was for the correct flag.
+        assertThat(changedFlag[0]).isEqualTo(flag);
+    }
+
+    @Test
+    public void testRemoveListener() {
+        Flag<?> flag = new BooleanFlag(1);
+        mFeatureFlags.addFlag(flag);
+
+        // Assert and capture that a plugin listener was added.
+        ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
+                ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
+
+        verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+        FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+
+        // Add a listener for the flag
+        final Flag<?>[] changedFlag = {null};
+        FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+        mFeatureFlags.addFlagListener(flag, listener);
+
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+
+        // Assert that the change was for the correct flag.
+        assertThat(changedFlag[0]).isEqualTo(flag);
+
+        changedFlag[0] = null;
+
+        // Now remove the listener.
+        mFeatureFlags.removeFlagListener(flag, listener);
+        // Signal a change.
+        pluginListener.onFlagChanged(flag.getId());
+        // Assert that the change was not triggered
+        assertThat(changedFlag[0]).isNull();
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 79b0dd0..8e1b13c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -69,7 +69,7 @@
         whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
                 .thenReturn(true)
         whenever(mediaHost.hostView).thenReturn(hostView)
-
+        hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
         keyguardMediaController = KeyguardMediaController(
             mediaHost,
             bypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 8fd2a32..3ea57be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -142,6 +142,8 @@
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultTrue() {
+        // Tablets may be using taskbar and the logic is different
+        mNavigationBarController.mIsTablet = false;
         doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(true, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt
new file mode 100644
index 0000000..9378b2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt
@@ -0,0 +1,91 @@
+package com.android.systemui.qs
+
+import com.android.systemui.R
+import android.os.UserManager
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.systemui.Dependency
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.utils.leaks.FakeTunerService
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class QSFooterActionsControllerTest : LeakCheckedTest() {
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock
+    private lateinit var userInfoController: UserInfoController
+    @Mock
+    private lateinit var qsPanelController: QSPanelController
+    @Mock
+    private lateinit var multiUserSwitchController: MultiUserSwitchController
+    @Mock
+    private lateinit var globalActionsDialog: GlobalActionsDialogLite
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Mock
+    private lateinit var controller: QSFooterActionsController
+
+    private val metricsLogger: MetricsLogger = FakeMetricsLogger()
+    private lateinit var view: QSFooterActionsView
+    private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
+        val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
+
+        view = LayoutInflater.from(context)
+                .inflate(R.layout.qs_footer_actions, null) as QSFooterActionsView
+
+        controller = QSFooterActionsController(view, qsPanelController, activityStarter,
+                userManager, userInfoController, multiUserSwitchController,
+                deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
+                globalActionsDialog, uiEventLogger, showPMLiteButton = true)
+        controller.init()
+        controller.onViewAttached()
+    }
+
+    @Test
+    fun testLogPowerMenuClick() {
+        controller.expanded = true
+        falsingManager.setFalseTap(false)
+
+        view.findViewById<View>(R.id.pm_lite).performClick()
+        // Verify clicks are logged
+        verify(uiEventLogger, Mockito.times(1))
+                .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+    }
+
+    @Test
+    fun testSettings_UserNotSetup() {
+        whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
+        view.findViewById<View>(R.id.settings_button).performClick()
+        // Verify Settings wasn't launched.
+        verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 6f7bf3b..8c6c358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -18,16 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -35,21 +30,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.utils.leaks.FakeTunerService;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
@@ -67,14 +49,6 @@
     @Mock
     private QSFooterView mView;
     @Mock
-    private UserManager mUserManager;
-    @Mock
-    private ActivityStarter mActivityStarter;
-    @Mock
-    private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock
-    private UserInfoController mUserInfoController;
-    @Mock
     private UserTracker mUserTracker;
     @Mock
     private QSPanelController mQSPanelController;
@@ -82,36 +56,19 @@
     private ClipboardManager mClipboardManager;
     @Mock
     private QuickQSPanelController mQuickQSPanelController;
-    private FakeTunerService mFakeTunerService;
-    private MetricsLogger mMetricsLogger = new FakeMetricsLogger();
-    private FalsingManagerFake mFalsingManager;
-
-    @Mock
-    private SettingsButton mSettingsButton;
     @Mock
     private TextView mBuildText;
     @Mock
-    private View mEdit;
-    @Mock
-    private MultiUserSwitchController mMultiUserSwitchController;
-    @Mock
-    private View mPowerMenuLiteView;
-    @Mock
-    private GlobalActionsDialogLite mGlobalActionsDialog;
-    @Mock
-    private UiEventLogger mUiEventLogger;
+    private QSFooterActionsController mQSFooterActionsController;
 
     private QSFooterViewController mController;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mFalsingManager = new FalsingManagerFake();
 
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
 
-        mFakeTunerService = (FakeTunerService) Dependency.get(TunerService.class);
-
         mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
 
         when(mView.getContext()).thenReturn(mContext);
@@ -119,16 +76,10 @@
         when(mUserTracker.getUserContext()).thenReturn(mContext);
 
         when(mView.isAttachedToWindow()).thenReturn(true);
-        when(mView.findViewById(R.id.settings_button)).thenReturn(mSettingsButton);
         when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
-        when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
-        when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView);
 
-        mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
-                mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
-                mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
-                mMetricsLogger, mFalsingManager, false, mGlobalActionsDialog,
-                mUiEventLogger);
+        mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController,
+                mQuickQSPanelController, mQSFooterActionsController);
 
         mController.init();
     }
@@ -148,40 +99,4 @@
         verify(mClipboardManager).setPrimaryClip(captor.capture());
         assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
     }
-
-    @Test
-    public void testSettings_UserNotSetup() {
-        ArgumentCaptor<View.OnClickListener> onClickCaptor =
-                ArgumentCaptor.forClass(View.OnClickListener.class);
-        verify(mSettingsButton).setOnClickListener(onClickCaptor.capture());
-
-        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
-
-        onClickCaptor.getValue().onClick(mSettingsButton);
-        // Verify Settings wasn't launched.
-        verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
-    }
-
-    @Test
-    public void testLogPowerMenuClick() {
-        // Enable power menu button
-        mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
-                mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
-                mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
-                mMetricsLogger, new FalsingManagerFake(), true, mGlobalActionsDialog,
-                mUiEventLogger);
-        mController.init();
-        mController.setExpanded(true);
-        mFalsingManager.setFalseTap(false);
-
-        ArgumentCaptor<View.OnClickListener> onClickCaptor =
-                ArgumentCaptor.forClass(View.OnClickListener.class);
-        verify(mPowerMenuLiteView).setOnClickListener(onClickCaptor.capture());
-
-        onClickCaptor.getValue().onClick(mPowerMenuLiteView);
-
-        // Verify clicks are logged
-        verify(mUiEventLogger, times(1))
-                .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
-    }
 }
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 f876a43..2b3624e 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
@@ -33,13 +33,13 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -79,7 +79,7 @@
     @Mock
     private GlobalSettings mGlobalSettings;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private NetworkController.AccessPointController mAccessPointController;
     @Mock
@@ -99,15 +99,16 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
         when(mWifiManager.getConnectionInfo()).thenReturn(mWifiInfo);
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
         when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
 
         mInternetDialogController = new MockInternetDialogController(mContext,
                 mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
-                mKeyguardUpdateMonitor, mGlobalSettings);
+                mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController);
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mInternetDialogController.mOnSubscriptionsChangedListener);
         mInternetDialogController.onStart(
@@ -174,6 +175,16 @@
     }
 
     @Test
+    public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+                getResourcesString("unlock_to_view_networks")));
+    }
+
+    @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
         mInternetDialogController.setAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
@@ -311,6 +322,20 @@
         verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
     }
 
+    @Test
+    public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+        assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+    }
+
+    @Test
+    public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+        assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
@@ -331,10 +356,12 @@
                 @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
                 @Main Handler handler, @Main Executor mainExecutor,
                 BroadcastDispatcher broadcastDispatcher,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings) {
+                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+                KeyguardStateController keyguardStateController) {
             super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
                     telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
-                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings);
+                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+                    keyguardStateController);
             mGlobalSettings = globalSettings;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 856e3a1..de2c7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -59,24 +59,31 @@
     private static final String WIFI_TITLE = "Connected Wi-Fi Title";
     private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
 
-    private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-
-    private InternetDialogFactory mInternetDialogFactory = mock(InternetDialogFactory.class);
-    private InternetAdapter mInternetAdapter = mock(InternetAdapter.class);
-    private InternetDialogController mInternetDialogController = mock(
-            InternetDialogController.class);
-    private InternetDialogController.InternetDialogCallback mCallback =
-            mock(InternetDialogController.InternetDialogCallback.class);
-    private MockInternetDialog mInternetDialog;
-    private WifiReceiver mWifiReceiver = null;
-    private WifiManager mMockWifiManager = mock(WifiManager.class);
-    private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
     @Mock
-    private WifiEntry mWifiEntry = mock(WifiEntry.class);
+    private InternetDialogFactory mInternetDialogFactory;
     @Mock
-    private WifiInfo mWifiInfo;
+    private InternetDialogController mInternetDialogController;
+    @Mock
+    private UiEventLogger mUiEventLogger;
     @Mock
     private Handler mHandler;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private InternetAdapter mInternetAdapter;
+    @Mock
+    private WifiManager mMockWifiManager;
+    @Mock
+    private WifiEntry mWifiEntry;
+    @Mock
+    private WifiInfo mWifiInfo;
+
+    private MockInternetDialog mInternetDialog;
+    private WifiReceiver mWifiReceiver;
+    private LinearLayout mWifiToggle;
+    private LinearLayout mConnectedWifi;
+    private RecyclerView mWifiList;
+    private LinearLayout mSeeAll;
 
     @Before
     public void setUp() {
@@ -99,6 +106,10 @@
         when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
         when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
         when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry));
+        mWifiToggle = mInternetDialog.mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+        mConnectedWifi = mInternetDialog.mDialogView.requireViewById(R.id.wifi_connected_layout);
+        mWifiList = mInternetDialog.mDialogView.requireViewById(R.id.wifi_list_layout);
+        mSeeAll = mInternetDialog.mDialogView.requireViewById(R.id.see_all_layout);
     }
 
     @After
@@ -137,7 +148,7 @@
     }
 
     @Test
-    public void updateDialog_withWifiOnAndHasConnectedWifi_connectedWifiLayoutVisible() {
+    public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifi() {
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
         when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
         when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
@@ -147,36 +158,85 @@
 
         mInternetDialog.updateDialog();
 
-        final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById(
-                R.id.wifi_connected_layout);
-        assertThat(linearLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
-    public void updateDialog_withWifiOnAndNoConnectedWifi_connectedWifiLayoutGone() {
+    public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-        mInternetDialog.updateDialog();
-        final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById(
-                R.id.wifi_connected_layout);
 
-        assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialog.updateDialog();
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
-    public void updateDialog_withWifiOff_WifiRecycleViewGone() {
-        when(mMockWifiManager.isWifiEnabled()).thenReturn(false);
-        mInternetDialog.updateDialog();
-        final RecyclerView view = mInternetDialog.mDialogView.requireViewById(
-                R.id.wifi_list_layout);
+    public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+        when(mInternetDialogController.getWifiEntryList()).thenReturn(null);
 
-        assertThat(view.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
-    public void onClickSeeMoreButton_clickSeeMore_verifyLaunchNetworkSetting() {
-        final LinearLayout seeAllLayout = mInternetDialog.mDialogView.requireViewById(
-                R.id.see_all_layout);
-        seeAllLayout.performClick();
+    public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+        List<WifiEntry> wifiEntries = new ArrayList<WifiEntry>();
+        wifiEntries.add(mWifiEntry);
+        when(mInternetDialogController.getWifiEntryList()).thenReturn(wifiEntries);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasConnectedWifi_showHighlightWifiToggle() {
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+        when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+        when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+        when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry.isDefaultNetwork()).thenReturn(true);
+        mInternetDialog.mConnectedWifiEntry = mWifiEntry;
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mWifiToggle.getBackground()).isNotNull();
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasConnectedWifi_hideConnectedWifi() {
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+        when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+        when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+        when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry.isDefaultNetwork()).thenReturn(true);
+        mInternetDialog.mConnectedWifiEntry = mWifiEntry;
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+        List<WifiEntry> wifiEntries = new ArrayList<WifiEntry>();
+        wifiEntries.add(mWifiEntry);
+        when(mInternetDialogController.getWifiEntryList()).thenReturn(wifiEntries);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+        mSeeAll.performClick();
 
         verify(mInternetDialogController).launchNetworkSetting();
     }
@@ -193,6 +253,17 @@
     }
 
     @Test
+    public void showProgressBar_deviceLocked_hideProgressBar() {
+        Mockito.reset(mHandler);
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+        mInternetDialog.showProgressBar();
+
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+        verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
     public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
         Mockito.reset(mHandler);
         when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index c365ef2..b1b9ff4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy
 
 import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
 import android.content.Context
 import android.content.DialogInterface
 import android.content.Intent
@@ -63,6 +64,8 @@
 @SmallTest
 class UserSwitcherControllerTest : SysuiTestCase() {
     @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var handler: Handler
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var userManager: UserManager
@@ -107,6 +110,8 @@
                 userManager,
                 userTracker,
                 keyguardStateController,
+                deviceProvisionedController,
+                devicePolicyManager,
                 handler,
                 activityStarter,
                 broadcastDispatcher,
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index cd332a6..4946ad4 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -125,7 +125,7 @@
             // connect with remote AppPredictionService instead for dark launch
             usesPeopleService = false;
         }
-        final boolean serviceExists = resolveService(sessionId, false,
+        final boolean serviceExists = resolveService(sessionId, true,
                 usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
         if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
             final AppPredictionSessionInfo sessionInfo = new AppPredictionSessionInfo(
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a56b1db..5aec6aa 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3285,57 +3285,6 @@
         }
     }
 
-    private void applyResourceOverlaysToWidgetsLocked(Set<String> packageNames, int userId,
-            boolean updateFrameworkRes) {
-        for (int i = 0, N = mProviders.size(); i < N; i++) {
-            Provider provider = mProviders.get(i);
-            if (provider.getUserId() != userId) {
-                continue;
-            }
-
-            final String packageName = provider.id.componentName.getPackageName();
-            if (!updateFrameworkRes && !packageNames.contains(packageName)) {
-                continue;
-            }
-
-            ApplicationInfo newAppInfo = null;
-            try {
-                newAppInfo = mPackageManager.getApplicationInfo(packageName,
-                        PackageManager.GET_SHARED_LIBRARY_FILES, userId);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to retrieve app info for " + packageName
-                        + " userId=" + userId, e);
-            }
-            if (newAppInfo == null) {
-                continue;
-            }
-            ApplicationInfo oldAppInfo = provider.info.providerInfo.applicationInfo;
-            if (!newAppInfo.sourceDir.equals(oldAppInfo.sourceDir)) {
-                // Overlay paths are generated against a particular version of an application.
-                // The overlays paths of a newly upgraded application are incompatible with the
-                // old version of the application.
-                continue;
-            }
-
-            // Isolate the changes relating to RROs. The app info must be copied to prevent
-            // affecting other parts of system server that may have cached this app info.
-            oldAppInfo = new ApplicationInfo(oldAppInfo);
-            oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
-            oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
-            provider.info.providerInfo.applicationInfo = oldAppInfo;
-
-            for (int j = 0, M = provider.widgets.size(); j < M; j++) {
-                Widget widget = provider.widgets.get(j);
-                if (widget.views != null) {
-                    widget.views.updateAppInfo(oldAppInfo);
-                }
-                if (widget.maskedViews != null) {
-                    widget.maskedViews.updateAppInfo(oldAppInfo);
-                }
-            }
-        }
-    }
-
     /**
      * Updates all providers with the specified package names, and records any providers that were
      * pruned.
@@ -4926,14 +4875,5 @@
         public void unlockUser(int userId) {
             handleUserUnlocked(userId);
         }
-
-        @Override
-        public void applyResourceOverlaysToWidgets(Set<String> packageNames, int userId,
-                boolean updateFrameworkRes) {
-            synchronized (mLock) {
-                applyResourceOverlaysToWidgetsLocked(new HashSet<>(packageNames), userId,
-                        updateFrameworkRes);
-            }
-        }
     }
 }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 0621d0f..e390ae28 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -353,6 +353,12 @@
 
 
     /**
+     * Retrieve all receivers that can handle a broadcast of the given intent.
+     */
+    public abstract List<ResolveInfo> queryIntentReceivers(Intent intent,
+            String resolvedType, int flags, int filterCallingUid, int userId);
+
+    /**
      * Retrieve all services that can be performed for the given intent.
      * @see PackageManager#queryIntentServices(Intent, int)
      */
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 047aae7..aae1cc0 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -147,6 +147,47 @@
         return true;
     }
 
+    /**
+     * Returns whether an intent matches the IntentFilter with a pre-resolved type.
+     */
+    public static boolean intentMatchesFilter(
+            IntentFilter filter, Intent intent, String resolvedType) {
+        final boolean debug = localLOGV
+                || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+        final Printer logPrinter = debug
+                ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null;
+
+        if (debug) {
+            Slog.v(TAG, "Intent: " + intent);
+            Slog.v(TAG, "Matching against filter: " + filter);
+            filter.dump(logPrinter, "  ");
+        }
+
+        final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
+                intent.getData(), intent.getCategories(), TAG);
+
+        if (match >= 0) {
+            if (debug) {
+                Slog.v(TAG, "Filter matched!  match=0x" + Integer.toHexString(match));
+            }
+            return true;
+        } else {
+            if (debug) {
+                final String reason;
+                switch (match) {
+                    case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
+                    case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
+                    case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
+                    case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
+                    default: reason = "unknown reason"; break;
+                }
+                Slog.v(TAG, "Filter did not match: " + reason);
+            }
+            return false;
+        }
+    }
+
     private ArrayList<F> collectFilters(F[] array, IntentFilter matching) {
         ArrayList<F> res = null;
         if (array != null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35ed477..0d8cbaf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -193,7 +193,6 @@
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetManagerInternal;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -12628,76 +12627,72 @@
         int pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
 
         List<ResolveInfo> receivers = null;
-        try {
-            HashSet<ComponentName> singleUserReceivers = null;
-            boolean scannedFirstReceivers = false;
-            for (int user : users) {
-                // Skip users that have Shell restrictions
-                if (callingUid == SHELL_UID
-                        && mUserController.hasUserRestriction(
-                                UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
-                    continue;
-                }
-                List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
-                        .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
-                if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
-                    // If this is not the system user, we need to check for
-                    // any receivers that should be filtered out.
-                    for (int i=0; i<newReceivers.size(); i++) {
-                        ResolveInfo ri = newReceivers.get(i);
-                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
-                            newReceivers.remove(i);
-                            i--;
-                        }
+        HashSet<ComponentName> singleUserReceivers = null;
+        boolean scannedFirstReceivers = false;
+        for (int user : users) {
+            // Skip users that have Shell restrictions
+            if (callingUid == SHELL_UID
+                    && mUserController.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+                continue;
+            }
+            List<ResolveInfo> newReceivers = mPackageManagerInt
+                    .queryIntentReceivers(intent, resolvedType, pmFlags, callingUid, user);
+            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+                // If this is not the system user, we need to check for
+                // any receivers that should be filtered out.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+                        newReceivers.remove(i);
+                        i--;
                     }
                 }
-                if (newReceivers != null && newReceivers.size() == 0) {
-                    newReceivers = null;
-                }
-                if (receivers == null) {
-                    receivers = newReceivers;
-                } else if (newReceivers != null) {
-                    // We need to concatenate the additional receivers
-                    // found with what we have do far.  This would be easy,
-                    // but we also need to de-dup any receivers that are
-                    // singleUser.
-                    if (!scannedFirstReceivers) {
-                        // Collect any single user receivers we had already retrieved.
-                        scannedFirstReceivers = true;
-                        for (int i=0; i<receivers.size(); i++) {
-                            ResolveInfo ri = receivers.get(i);
-                            if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                                ComponentName cn = new ComponentName(
-                                        ri.activityInfo.packageName, ri.activityInfo.name);
-                                if (singleUserReceivers == null) {
-                                    singleUserReceivers = new HashSet<ComponentName>();
-                                }
-                                singleUserReceivers.add(cn);
-                            }
-                        }
-                    }
-                    // Add the new results to the existing results, tracking
-                    // and de-dupping single user receivers.
-                    for (int i=0; i<newReceivers.size(); i++) {
-                        ResolveInfo ri = newReceivers.get(i);
+            }
+            if (newReceivers != null && newReceivers.size() == 0) {
+                newReceivers = null;
+            }
+            if (receivers == null) {
+                receivers = newReceivers;
+            } else if (newReceivers != null) {
+                // We need to concatenate the additional receivers
+                // found with what we have do far.  This would be easy,
+                // but we also need to de-dup any receivers that are
+                // singleUser.
+                if (!scannedFirstReceivers) {
+                    // Collect any single user receivers we had already retrieved.
+                    scannedFirstReceivers = true;
+                    for (int i = 0; i < receivers.size(); i++) {
+                        ResolveInfo ri = receivers.get(i);
                         if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
                             ComponentName cn = new ComponentName(
                                     ri.activityInfo.packageName, ri.activityInfo.name);
                             if (singleUserReceivers == null) {
                                 singleUserReceivers = new HashSet<ComponentName>();
                             }
-                            if (!singleUserReceivers.contains(cn)) {
-                                singleUserReceivers.add(cn);
-                                receivers.add(ri);
-                            }
-                        } else {
-                            receivers.add(ri);
+                            singleUserReceivers.add(cn);
                         }
                     }
                 }
+                // Add the new results to the existing results, tracking
+                // and de-dupping single user receivers.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                        ComponentName cn = new ComponentName(
+                                ri.activityInfo.packageName, ri.activityInfo.name);
+                        if (singleUserReceivers == null) {
+                            singleUserReceivers = new HashSet<ComponentName>();
+                        }
+                        if (!singleUserReceivers.contains(cn)) {
+                            singleUserReceivers.add(cn);
+                            receivers.add(ri);
+                        }
+                    } else {
+                        receivers.add(ri);
+                    }
+                }
             }
-        } catch (RemoteException ex) {
-            // pm is in same process, this will never happen.
         }
         if (receivers != null && broadcastAllowList != null) {
             for (int i = receivers.size() - 1; i >= 0; i--) {
@@ -13331,8 +13326,7 @@
         List receivers = null;
         List<BroadcastFilter> registeredReceivers = null;
         // Need to resolve the intent to interested receivers...
-        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
-                 == 0) {
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
             receivers = collectReceiverComponents(
                     intent, resolvedType, callingUid, users, broadcastAllowList);
         }
@@ -16658,13 +16652,6 @@
         if (updateFrameworkRes) {
             ParsingPackageUtils.readConfigUseRoundIcon(null);
         }
-
-        AppWidgetManagerInternal widgets = LocalServices.getService(AppWidgetManagerInternal.class);
-        if (widgets != null) {
-            widgets.applyResourceOverlaysToWidgets(new HashSet<>(packagesToUpdate), userId,
-                    updateFrameworkRes);
-        }
-
         mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes);
 
         if (updateFrameworkRes) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fcd198e..2f69abf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -738,6 +738,11 @@
     private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
     private long mLoweredFromNormalToVibrateTime;
 
+    // Uid of the active hotword detection service to check if caller is the one or not.
+    @GuardedBy("mHotwordDetectionServiceUidLock")
+    private int mHotwordDetectionServiceUid = android.os.Process.INVALID_UID;
+    private final Object mHotwordDetectionServiceUidLock = new Object();
+
     // Array of Uids of valid accessibility services to check if caller is one of them
     private final Object mAccessibilityServiceUidsLock = new Object();
     @GuardedBy("mAccessibilityServiceUidsLock")
@@ -1339,6 +1344,9 @@
             updateAssistantUId(true);
             AudioSystem.setRttEnabled(mRttEnabled);
         }
+        synchronized (mHotwordDetectionServiceUidLock) {
+            AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
+        }
         synchronized (mAccessibilityServiceUidsLock) {
             AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
         }
@@ -9082,6 +9090,16 @@
         }
 
         @Override
+        public void setHotwordDetectionServiceUid(int uid) {
+            synchronized (mHotwordDetectionServiceUidLock) {
+                if (mHotwordDetectionServiceUid != uid) {
+                    mHotwordDetectionServiceUid = uid;
+                    AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
+                }
+            }
+        }
+
+        @Override
         public void setAccessibilityServiceUids(IntArray uids) {
             synchronized (mAccessibilityServiceUidsLock) {
                 if (uids.size() == 0) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 973fbd2..6d56780 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -367,6 +367,14 @@
     }
 
     /**
+     * Same as {@link AudioSystem#setHotwordDetectionServiceUid(int)}
+     * Communicate UID of current HotwordDetectionService to audio policy service.
+     */
+    public int setHotwordDetectionServiceUid(int uid) {
+        return AudioSystem.setHotwordDetectionServiceUid(uid);
+    }
+
+    /**
      * Same as {@link AudioSystem#setCurrentImeUid(int)}
      * Communicate UID of current InputMethodService to audio policy service.
      */
diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java
new file mode 100644
index 0000000..4d36078
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java
@@ -0,0 +1,176 @@
+/*
+ * 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.hdmi;
+
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Feature action that queries the Short Audio Descriptor (SAD) of another device. This action is
+ * initiated from the Android system working as TV device to get the SAD of the connected audio
+ * system device.
+ * <p>
+ * Package-private
+ */
+final class RequestSadAction extends HdmiCecFeatureAction {
+    private static final String TAG = "RequestSadAction";
+
+    // State in which the action is waiting for <Report Short Audio Descriptor>.
+    private static final int STATE_WAITING_FOR_REPORT_SAD = 1;
+
+    private static final List<Integer> ALL_CEC_CODECS = new ArrayList<Integer>(Arrays.asList(
+            Constants.AUDIO_CODEC_LPCM,
+            Constants.AUDIO_CODEC_DD,
+            Constants.AUDIO_CODEC_MPEG1,
+            Constants.AUDIO_CODEC_MP3,
+            Constants.AUDIO_CODEC_MPEG2,
+            Constants.AUDIO_CODEC_AAC,
+            Constants.AUDIO_CODEC_DTS,
+            Constants.AUDIO_CODEC_ATRAC,
+            Constants.AUDIO_CODEC_ONEBITAUDIO,
+            Constants.AUDIO_CODEC_DDP,
+            Constants.AUDIO_CODEC_DTSHD,
+            Constants.AUDIO_CODEC_TRUEHD,
+            Constants.AUDIO_CODEC_DST,
+            Constants.AUDIO_CODEC_WMAPRO,
+            Constants.AUDIO_CODEC_MAX));
+    private static final int MAX_SAD_PER_REQUEST = 4;
+    private static final int RETRY_COUNTER_MAX = 1;
+    private final int mTargetAddress;
+    private final RequestSadCallback mCallback;
+    // List of all valid SADs reported by the target device. Not parsed nor deduplicated.
+    private final List<byte[]> mSupportedSads = new ArrayList<>();
+    private int mQueriedSadCount = 0; // Number of SADs queries that has already been completed
+    private int mTimeoutRetry = 0; // Number of times we have already retried on time-out
+
+    /**
+     * Constructor.
+     *
+     * @param source        an instance of {@link HdmiCecLocalDevice}.
+     * @param targetAddress the logical address the SAD is directed at.
+     */
+    RequestSadAction(HdmiCecLocalDevice source, int targetAddress, RequestSadCallback callback) {
+        super(source);
+        mTargetAddress = targetAddress;
+        mCallback = Objects.requireNonNull(callback);
+    }
+
+
+    @Override
+    boolean start() {
+        querySad();
+        return true;
+    }
+
+    private void querySad() {
+        if (mQueriedSadCount >= ALL_CEC_CODECS.size()) {
+            wrapUpAndFinish();
+            return;
+        }
+        int[] codecsToQuery = ALL_CEC_CODECS.subList(mQueriedSadCount,
+                Math.min(ALL_CEC_CODECS.size(), mQueriedSadCount + MAX_SAD_PER_REQUEST))
+                    .stream().mapToInt(i -> i).toArray();
+        sendCommand(HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(getSourceAddress(),
+                mTargetAddress, codecsToQuery));
+        mState = STATE_WAITING_FOR_REPORT_SAD;
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAITING_FOR_REPORT_SAD
+                || mTargetAddress != cmd.getSource()) {
+            return false;
+        }
+        if (cmd.getOpcode() == Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR) {
+            if (cmd.getParams() == null || cmd.getParams().length == 0
+                    || cmd.getParams().length % 3 != 0) {
+                // Invalid message. Wait for time-out and query again.
+                return true;
+            }
+            for (int i = 0; i < cmd.getParams().length - 2; i += 3) {
+                if (isValidCodec(cmd.getParams()[i])) {
+                    byte[] sad = new byte[]{cmd.getParams()[i], cmd.getParams()[i + 1],
+                            cmd.getParams()[i + 2]};
+                    updateResult(sad);
+                }
+                // Don't include invalid codecs in the result. Don't query again.
+                Slog.w(TAG, "Received invalid codec " + cmd.getParams()[i] + ".");
+            }
+            mQueriedSadCount += MAX_SAD_PER_REQUEST;
+            mTimeoutRetry = 0;
+            querySad();
+            return true;
+        }
+        if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT
+                && (cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR
+                && (cmd.getParams()[1] & 0xFF) == Constants.ABORT_INVALID_OPERAND) {
+            // Queried SADs are not supported
+            mQueriedSadCount += MAX_SAD_PER_REQUEST;
+            mTimeoutRetry = 0;
+            querySad();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isValidCodec(byte codec) {
+        return Constants.AUDIO_CODEC_NONE < (codec & 0xFF)
+                && (codec & 0xFF) <= Constants.AUDIO_CODEC_MAX;
+    }
+
+    private void updateResult(byte[] sad) {
+        mSupportedSads.add(sad);
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+        if (state == STATE_WAITING_FOR_REPORT_SAD) {
+            if (++mTimeoutRetry <= RETRY_COUNTER_MAX) {
+                querySad();
+                return;
+            }
+            mQueriedSadCount += MAX_SAD_PER_REQUEST;
+            mTimeoutRetry = 0;
+            querySad();
+        }
+    }
+
+    private void wrapUpAndFinish() {
+        mCallback.onRequestSadDone(mSupportedSads);
+        finish();
+    }
+
+    /**
+     * Interface used to report result of SAD request.
+     */
+    interface RequestSadCallback {
+        /**
+         * Called when SAD request is done.
+         *
+         * @param sads a list of all supported SADs. It can be an empty list.
+         */
+        void onRequestSadDone(List<byte[]> supportedSads);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 14dc876..d2c15ab 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -289,6 +289,7 @@
 import android.util.PackageUtils;
 import android.util.Pair;
 import android.util.PrintStreamPrinter;
+import android.util.Printer;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -327,6 +328,7 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
+import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.PackageWatchdog;
@@ -701,6 +703,20 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
     private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
 
+    /**
+     * Components of apps targeting Android T and above will stop receiving intents from
+     * external callers that do not match its declared intent filters.
+     *
+     * When an app registers an exported component in its manifest and adds an <intent-filter>,
+     * the component can be started by any intent - even those that do not match the intent filter.
+     * This has proven to be something that many developers find counterintuitive.
+     * Without checking the intent when the component is started, in some circumstances this can
+     * allow 3P apps to trigger internal-only functionality.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+    private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
+
     public static final String PLATFORM_PACKAGE_NAME = "android";
 
     static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -2160,9 +2176,11 @@
                     false /* requireFullPermission */, false /* checkShell */,
                     "query intent activities");
             final String pkgName = intent.getPackage();
+            Intent originalIntent = null;
             ComponentName comp = intent.getComponent();
             if (comp == null) {
                 if (intent.getSelector() != null) {
+                    originalIntent = intent;
                     intent = intent.getSelector();
                     comp = intent.getComponent();
                 }
@@ -2172,8 +2190,9 @@
                     comp != null || pkgName != null /*onlyExposedExplicitly*/,
                     isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
                             flags));
+            List<ResolveInfo> list = Collections.emptyList();
+            boolean skipPostResolution = false;
             if (comp != null) {
-                final List<ResolveInfo> list = new ArrayList<>(1);
                 final ActivityInfo ai = getActivityInfo(comp, flags, userId);
                 if (ai != null) {
                     // When specifying an explicit component, we prevent the activity from being
@@ -2216,35 +2235,45 @@
                     if (!blockInstantResolution && !blockNormalResolution) {
                         final ResolveInfo ri = new ResolveInfo();
                         ri.activityInfo = ai;
+                        list = new ArrayList<>(1);
                         list.add(ri);
+                        applyEnforceIntentFilterMatching(
+                                mInjector.getCompatibility(), mComponentResolver,
+                                list, false, intent, resolvedType, filterCallingUid);
                     }
                 }
-
-                return applyPostResolutionFilter(
-                        list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
-                        resolveForStart,
-                        userId, intent);
+            } else {
+                QueryIntentActivitiesResult lockedResult =
+                        queryIntentActivitiesInternalBody(
+                                intent, resolvedType, flags, filterCallingUid, userId,
+                                resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
+                if (lockedResult.answer != null) {
+                    skipPostResolution = true;
+                    list = lockedResult.answer;
+                } else {
+                    if (lockedResult.addInstant) {
+                        String callingPkgName = getInstantAppPackageName(filterCallingUid);
+                        boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+                        lockedResult.result = maybeAddInstantAppInstaller(
+                                lockedResult.result, intent, resolvedType, flags,
+                                userId, resolveForStart, isRequesterInstantApp);
+                    }
+                    if (lockedResult.sortResult) {
+                        lockedResult.result.sort(RESOLVE_PRIORITY_SORTER);
+                    }
+                    list = lockedResult.result;
+                }
             }
 
-            QueryIntentActivitiesResult lockedResult =
-                    queryIntentActivitiesInternalBody(
-                        intent, resolvedType, flags, filterCallingUid, userId, resolveForStart,
-                        allowDynamicSplits, pkgName, instantAppPkgName);
-            if (lockedResult.answer != null) {
-                return lockedResult.answer;
+            if (originalIntent != null) {
+                // We also have to ensure all components match the original intent
+                applyEnforceIntentFilterMatching(
+                        mInjector.getCompatibility(), mComponentResolver,
+                        list, false, originalIntent, resolvedType, filterCallingUid);
             }
 
-            if (lockedResult.addInstant) {
-                String callingPkgName = getInstantAppPackageName(filterCallingUid);
-                boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
-                lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent,
-                        resolvedType, flags, userId, resolveForStart, isRequesterInstantApp);
-            }
-            if (lockedResult.sortResult) {
-                Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER);
-            }
-            return applyPostResolutionFilter(
-                    lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid,
+            return skipPostResolution ? list : applyPostResolutionFilter(
+                    list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
                     resolveForStart, userId, intent);
         }
 
@@ -2267,15 +2296,17 @@
             final String instantAppPkgName = getInstantAppPackageName(callingUid);
             flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
                     false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+            Intent originalIntent = null;
             ComponentName comp = intent.getComponent();
             if (comp == null) {
                 if (intent.getSelector() != null) {
+                    originalIntent = intent;
                     intent = intent.getSelector();
                     comp = intent.getComponent();
                 }
             }
+            List<ResolveInfo> list = Collections.emptyList();
             if (comp != null) {
-                final List<ResolveInfo> list = new ArrayList<>(1);
                 final ServiceInfo si = getServiceInfo(comp, flags, userId);
                 if (si != null) {
                     // When specifying an explicit component, we prevent the service from being
@@ -2308,14 +2339,26 @@
                     if (!blockInstantResolution && !blockNormalResolution) {
                         final ResolveInfo ri = new ResolveInfo();
                         ri.serviceInfo = si;
+                        list = new ArrayList<>(1);
                         list.add(ri);
+                        applyEnforceIntentFilterMatching(
+                                mInjector.getCompatibility(), mComponentResolver,
+                                list, false, intent, resolvedType, callingUid);
                     }
                 }
-                return list;
+            } else {
+                list = queryIntentServicesInternalBody(intent, resolvedType, flags,
+                        userId, callingUid, instantAppPkgName);
             }
 
-            return queryIntentServicesInternalBody(intent, resolvedType, flags,
-                    userId, callingUid, instantAppPkgName);
+            if (originalIntent != null) {
+                // We also have to ensure all components match the original intent
+                applyEnforceIntentFilterMatching(
+                        mInjector.getCompatibility(), mComponentResolver,
+                        list, false, originalIntent, resolvedType, callingUid);
+            }
+
+            return list;
         }
 
         protected @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
@@ -5544,10 +5587,8 @@
             }
         }
     }
-    private static final ThreadLocal<ThreadComputer> sThreadComputer = new ThreadLocal<>() {
-            @Override protected ThreadComputer initialValue() {
-                return new ThreadComputer();
-            }};
+    private static final ThreadLocal<ThreadComputer> sThreadComputer =
+            ThreadLocal.withInitial(ThreadComputer::new);
 
     /**
      * This lock is used to make reads from {@link #sSnapshotInvalid} and
@@ -8136,7 +8177,7 @@
 
         final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
                 MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+                UserHandle.USER_SYSTEM, Binder.getCallingUid());
         if (matches.size() == 1) {
             return matches.get(0).getComponentInfo().packageName;
         } else if (matches.size() == 0) {
@@ -8232,7 +8273,7 @@
 
         final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
                 MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+                UserHandle.USER_SYSTEM, Binder.getCallingUid());
         ResolveInfo best = null;
         final int N = matches.size();
         for (int i = 0; i < N; i++) {
@@ -8260,7 +8301,7 @@
         Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
         List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
                 MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+                UserHandle.USER_SYSTEM, Binder.getCallingUid());
         ResolveInfo best = null;
         final int N = matches.size();
         for (int i = 0; i < N; i++) {
@@ -10939,30 +10980,32 @@
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
             String resolvedType, int flags, int userId) {
-        return new ParceledListSlice<>(
-                queryIntentReceiversInternal(intent, resolvedType, flags, userId,
-                        false /*allowDynamicSplits*/));
+        return new ParceledListSlice<>(queryIntentReceiversInternal(intent, resolvedType,
+                flags, userId, Binder.getCallingUid()));
     }
 
+    // In this method, we have to know the actual calling UID, but in some cases Binder's
+    // call identity is removed, so the UID has to be passed in explicitly.
     private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
-            String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
+            String resolvedType, int flags, int userId, int filterCallingUid) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+        enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
                 false /*checkShell*/, "query intent receivers");
-        final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+        final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
+        flags = updateFlagsForResolve(flags, userId, filterCallingUid, false /*includeInstantApps*/,
                 isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
                         flags));
+        Intent originalIntent = null;
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
+                originalIntent = intent;
                 intent = intent.getSelector();
                 comp = intent.getComponent();
             }
         }
+        List<ResolveInfo> list = Collections.emptyList();
         if (comp != null) {
-            final List<ResolveInfo> list = new ArrayList<>(1);
             final ActivityInfo ai = getReceiverInfo(comp, flags, userId);
             if (ai != null) {
                 // When specifying an explicit component, we prevent the activity from being
@@ -10998,40 +11041,44 @@
                 if (!blockResolution) {
                     ResolveInfo ri = new ResolveInfo();
                     ri.activityInfo = ai;
+                    list = new ArrayList<>(1);
                     list.add(ri);
+                    applyEnforceIntentFilterMatching(
+                            mInjector.getCompatibility(), mComponentResolver,
+                            list, true, intent, resolvedType, filterCallingUid);
                 }
             }
-            return applyPostResolutionFilter(
-                    list, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
-                    intent);
+        } else {
+            // reader
+            synchronized (mLock) {
+                String pkgName = intent.getPackage();
+                if (pkgName == null) {
+                    final List<ResolveInfo> result =
+                            mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
+                    if (result != null) {
+                        list = result;
+                    }
+                }
+                final AndroidPackage pkg = mPackages.get(pkgName);
+                if (pkg != null) {
+                    final List<ResolveInfo> result = mComponentResolver.queryReceivers(
+                            intent, resolvedType, flags, pkg.getReceivers(), userId);
+                    if (result != null) {
+                        list = result;
+                    }
+                }
+            }
         }
 
-        // reader
-        synchronized (mLock) {
-            String pkgName = intent.getPackage();
-            if (pkgName == null) {
-                final List<ResolveInfo> result =
-                        mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
-                if (result == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostResolutionFilter(
-                        result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
-                        intent);
-            }
-            final AndroidPackage pkg = mPackages.get(pkgName);
-            if (pkg != null) {
-                final List<ResolveInfo> result = mComponentResolver.queryReceivers(
-                        intent, resolvedType, flags, pkg.getReceivers(), userId);
-                if (result == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostResolutionFilter(
-                        result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
-                        intent);
-            }
-            return Collections.emptyList();
+        if (originalIntent != null) {
+            // We also have to ensure all components match the original intent
+            applyEnforceIntentFilterMatching(
+                    mInjector.getCompatibility(), mComponentResolver,
+                    list, true, originalIntent, resolvedType, filterCallingUid);
         }
+
+        return applyPostResolutionFilter(
+                list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
     }
 
     @Override
@@ -11073,6 +11120,68 @@
                 includeInstantApps);
     }
 
+    // Static to give access to ComputeEngine
+    private static void applyEnforceIntentFilterMatching(
+            PlatformCompat compat, ComponentResolver resolver,
+            List<ResolveInfo> resolveInfos, boolean isReceiver,
+            Intent intent, String resolvedType, int filterCallingUid) {
+        // Do not enforce filter matching when the caller is system or root.
+        // see ActivityManager#checkComponentPermission(String, int, int, boolean)
+        if (filterCallingUid == Process.ROOT_UID || filterCallingUid == Process.SYSTEM_UID) {
+            return;
+        }
+
+        final Printer logPrinter = DEBUG_INTENT_MATCHING
+                ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
+                : null;
+
+        for (int i = resolveInfos.size() - 1; i >= 0; --i) {
+            final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
+
+            // Do not enforce filter matching when the caller is the same app
+            if (info.applicationInfo.uid == filterCallingUid) {
+                continue;
+            }
+
+            // Only enforce filter matching if target app's target SDK >= T
+            if (!compat.isChangeEnabledInternal(
+                    ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
+                continue;
+            }
+
+            final ParsedMainComponent comp;
+            if (info instanceof ActivityInfo) {
+                if (isReceiver) {
+                    comp = resolver.getReceiver(info.getComponentName());
+                } else {
+                    comp = resolver.getActivity(info.getComponentName());
+                }
+            } else if (info instanceof ServiceInfo) {
+                comp = resolver.getService(info.getComponentName());
+            } else {
+                // This shall never happen
+                throw new IllegalArgumentException("Unsupported component type");
+            }
+
+            if (comp.getIntents().isEmpty()) {
+                continue;
+            }
+
+            final boolean match = comp.getIntents().stream().anyMatch(
+                    f -> IntentResolver.intentMatchesFilter(f, intent, resolvedType));
+            if (!match) {
+                Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
+                Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+                if (DEBUG_INTENT_MATCHING) {
+                    Slog.v(TAG, "Component intent filters:");
+                    comp.getIntents().forEach(f -> f.dump(logPrinter, "  "));
+                    Slog.v(TAG, "-----------------------------");
+                }
+                resolveInfos.remove(i);
+            }
+        }
+    }
+
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
             String resolvedType, int flags, int userId) {
@@ -23412,6 +23521,13 @@
         }
 
         @Override
+        public List<ResolveInfo> queryIntentReceivers(Intent intent,
+                String resolvedType, int flags, int filterCallingUid, int userId) {
+            return PackageManagerService.this.queryIntentReceiversInternal(intent, resolvedType,
+                    flags, userId, filterCallingUid);
+        }
+
+        @Override
         public List<ResolveInfo> queryIntentServices(
                 Intent intent, int flags, int callingUid, int userId) {
             final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 3eb4bde..2fcc4b2 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -55,6 +55,7 @@
  */
 public final class PackageImpl extends ParsingPackageImpl implements ParsedPackage, AndroidPackage {
 
+    @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
             @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
         return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp);
@@ -70,6 +71,7 @@
      * this case only cares about
      * volumeUuid, just fake it rather than having separate method paths.
      */
+    @NonNull
     public static AndroidPackage buildFakeForDeletion(String packageName, String volumeUuid) {
         return ((ParsedPackage) PackageImpl.forTesting(packageName)
                 .setVolumeUuid(volumeUuid)
@@ -77,11 +79,13 @@
                 .hideAsFinal();
     }
 
+    @NonNull
     @VisibleForTesting
     public static ParsingPackage forTesting(String packageName) {
         return forTesting(packageName, "");
     }
 
+    @NonNull
     @VisibleForTesting
     public static ParsingPackage forTesting(String packageName, String baseCodePath) {
         return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false);
@@ -568,6 +572,7 @@
         assignDerivedFields();
     }
 
+    @NonNull
     public static final Creator<PackageImpl> CREATOR = new Creator<PackageImpl>() {
         @Override
         public PackageImpl createFromParcel(Parcel source) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e6e18fd..37003ad 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3687,7 +3687,7 @@
 
     @Override
     void removeImmediately() {
-        if (!finishing) {
+        if (!isState(DESTROYING, DESTROYED)) {
             // If Task#removeImmediately is called directly with alive activities, ensure that the
             // activities are destroyed and detached from process.
             destroyImmediately("removeImmediately");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 25827cf..19cf14f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1908,15 +1908,15 @@
 
         if (r.moveFocusableActivityToTop("setFocusedTask")) {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
-        } else if (touchedActivity != null && touchedActivity != r
-                && touchedActivity.getTask() == r.getTask()
-                && touchedActivity.getTaskFragment() != r.getTaskFragment()) {
-            // Set the focused app directly since the focused window is not on the
-            // top-most TaskFragment of the top-most Task
-            final DisplayContent displayContent = touchedActivity.getDisplayContent();
-            displayContent.setFocusedApp(touchedActivity);
-            mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
-                    true /* updateInputWindows */);
+        } else if (touchedActivity != null && touchedActivity.isFocusable()) {
+            final TaskFragment parent = touchedActivity.getTaskFragment();
+            if (parent != null && parent.isEmbedded()) {
+                // Set the focused app directly if the focused window is currently embedded
+                final DisplayContent displayContent = touchedActivity.getDisplayContent();
+                displayContent.setFocusedApp(touchedActivity);
+                mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                        true /* updateInputWindows */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b2e7b8f..31f943b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -771,10 +771,19 @@
                 return true;
             }
 
-            if (focusedApp.getTask() == activity.getTask()
-                    && focusedApp.getTaskFragment() != activity.getTaskFragment()) {
-                // Do not use the activity window of another TaskFragment in the same leaf Task
-                return false;
+            // If the candidate activity is currently being embedded in the focused task, the
+            // activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.
+            TaskFragment parent = activity.getTaskFragment();
+            if (parent != null && parent.isEmbedded()) {
+                Task hostTask = focusedApp.getTask();
+                if (hostTask.isEmbedded()) {
+                    // Use the hosting task if the current task is embedded.
+                    hostTask = hostTask.getParent().asTaskFragment().getTask();
+                }
+                if (activity.isDescendantOf(hostTask)
+                        && activity.getTaskFragment() != focusedApp.getTaskFragment()) {
+                    return false;
+                }
             }
         }
 
@@ -4577,7 +4586,9 @@
                 return true;
             }
 
-            if (task.isOrganized()) {
+            // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove
+            // the special case when it does.
+            if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index eb5ab83..3bb72b2 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -306,7 +306,10 @@
         boolean useSurfaceCrop = false;
         final Task task = w.getTask();
         if (task != null) {
-            if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+            // TODO(b/165794636): Remove the special case for freeform window once drag resizing is
+            // handled by WM shell.
+            if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+                        && !task.inFreeformWindowingMode()) {
                 // If the window is in a TaskManaged by a TaskOrganizer then most cropping will
                 // be applied using the SurfaceControl hierarchy from the Organizer. This means
                 // we need to make sure that these changes in crop are reflected in the input
@@ -538,7 +541,8 @@
             if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
                 if (recentsAnimationController.updateInputConsumerForApp(
                         mRecentsAnimationInputConsumer.mWindowHandle)) {
-                    mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord);
+                    mRecentsAnimationInputConsumer.show(mInputTransaction,
+                            recentsAnimationController.getHighestLayerTask());
                     mAddRecentsAnimationInputConsumerHandle = false;
                 }
             }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 895a82b..53af563 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -30,10 +30,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.StatusBarManager;
@@ -222,7 +218,7 @@
     InsetsState getInsetsForWindow(WindowState target) {
         final InsetsState originalState = mStateController.getInsetsForWindow(target);
         final InsetsState state = adjustVisibilityForTransientTypes(originalState);
-        return adjustVisibilityForIme(target, state, state == originalState);
+        return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
     }
 
     /**
@@ -252,38 +248,16 @@
         return state;
     }
 
-    private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
+    // Navigation bar insets is always visible to IME.
+    private static InsetsState adjustVisibilityForIme(InsetsState originalState,
             boolean copyState) {
-        if (w.mIsImWindow) {
-            // Navigation bar insets is always visible to IME.
-            final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
-            if (originalNavSource != null && !originalNavSource.isVisible()) {
-                final InsetsState state = copyState ? new InsetsState(originalState)
-                        : originalState;
-                final InsetsSource navSource = new InsetsSource(originalNavSource);
-                navSource.setVisible(true);
-                state.addSource(navSource);
-                return state;
-            }
-        } else if (w.mActivityRecord != null && !w.mActivityRecord.mLastImeShown) {
-            // During switching tasks with gestural navigation, if the IME is attached to
-            // one app window on that time, even the next app window is behind the IME window,
-            // conceptually the window should not receive the IME insets if the next window is
-            // not eligible IME requester and ready to show IME on top of it.
-            final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
-            final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
-
-            if (originalImeSource != null && shouldImeAttachedToApp
-                    && (w.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS)
-                            || !w.getRequestedVisibility(ITYPE_IME))) {
-                final InsetsState state = copyState ? new InsetsState(originalState)
-                        : originalState;
-
-                final InsetsSource imeSource = new InsetsSource(originalImeSource);
-                imeSource.setVisible(false);
-                state.addSource(imeSource);
-                return state;
-            }
+        final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
+        if (originalNavSource != null && !originalNavSource.isVisible()) {
+            final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
+            final InsetsSource navSource = new InsetsSource(originalNavSource);
+            navSource.setVisible(true);
+            state.addSource(navSource);
+            return state;
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 4f93c7a..c457df9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -1102,6 +1102,23 @@
         return mTargetActivityRecord.findMainWindow();
     }
 
+    /**
+     * Returns the task with the highest layer, or null if none is found.
+     */
+    public Task getHighestLayerTask() {
+        int highestLayer = Integer.MIN_VALUE;
+        Task highestLayerTask = null;
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            TaskAnimationAdapter adapter = mPendingAnimations.get(i);
+            int layer = adapter.mTask.getPrefixOrderIndex();
+            if (layer > highestLayer) {
+                highestLayer = layer;
+                highestLayerTask = adapter.mTask;
+            }
+        }
+        return highestLayerTask;
+    }
+
     boolean isAnimatingTask(Task task) {
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             if (task == mPendingAnimations.get(i).mTask) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 69ad59a..fec7ede 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3252,7 +3252,7 @@
             boolean consumed = false;
             if (traverseTopToBottom) {
                 for (int i = task.mChildren.size() - 1; i >= 0; --i) {
-                    final WindowContainer child = mChildren.get(i);
+                    final WindowContainer child = task.mChildren.get(i);
                     if (child.asTaskFragment() != null) {
                         child.forAllLeafTaskFragments(callback, traverseTopToBottom);
                     } else if (child.asActivityRecord() != null && !consumed) {
@@ -3262,7 +3262,7 @@
                 }
             } else {
                 for (int i = 0; i < task.mChildren.size(); i++) {
-                    final WindowContainer child = mChildren.get(i);
+                    final WindowContainer child = task.mChildren.get(i);
                     if (child.asTaskFragment() != null) {
                         child.forAllLeafTaskFragments(callback, traverseTopToBottom);
                     } else if (child.asActivityRecord() != null && !consumed) {
@@ -4174,8 +4174,7 @@
     }
 
     private boolean canBeOrganized() {
-        if (mForceNotOrganized || !mAtmService.mTaskOrganizerController
-                .isSupportedWindowingMode(getWindowingMode())) {
+        if (mForceNotOrganized) {
             return false;
         }
         // All root tasks can be organized
@@ -4332,7 +4331,7 @@
 
         final int windowingMode = getWindowingMode();
         final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController;
-        final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode);
+        final ITaskOrganizer organizer = controller.getTaskOrganizer();
         if (!forceUpdate && mTaskOrganizer == organizer) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 99f31ad..b17a939 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
@@ -67,14 +64,6 @@
 class TaskOrganizerController extends ITaskOrganizerController.Stub {
     private static final String TAG = "TaskOrganizerController";
 
-    // The set of modes that are currently supports
-    // TODO: Remove once the task organizer can support all modes
-    @VisibleForTesting
-    static final int[] UNSUPPORTED_WINDOWING_MODES = {
-            WINDOWING_MODE_UNDEFINED,
-            WINDOWING_MODE_FREEFORM
-    };
-
     private class DeathRecipient implements IBinder.DeathRecipient {
         ITaskOrganizer mTaskOrganizer;
 
@@ -373,11 +362,6 @@
 
                 final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 mService.mRootWindowContainer.forAllTasks((task) -> {
-                    if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES,
-                            task.getWindowingMode())) {
-                        return;
-                    }
-
                     boolean returnTask = !task.mCreatedByOrganizer;
                     task.updateTaskOrganizerState(true /* forceUpdate */,
                             returnTask /* skipTaskAppeared */);
@@ -433,14 +417,8 @@
     /**
      * @return the task organizer key for a given windowing mode.
      */
-    ITaskOrganizer getTaskOrganizer(int windowingMode) {
-        return isSupportedWindowingMode(windowingMode)
-                ? mTaskOrganizers.peekLast()
-                : null;
-    }
-
-    boolean isSupportedWindowingMode(int winMode) {
-        return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
+    ITaskOrganizer getTaskOrganizer() {
+        return mTaskOrganizers.peekLast();
     }
 
     // Capture the animation surface control for activity's main window
@@ -993,9 +971,6 @@
             for (int k = 0; k < tasks.size(); k++) {
                 final Task task = tasks.get(k);
                 final int mode = task.getWindowingMode();
-                if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, mode)) {
-                    continue;
-                }
                 pw.println(innerPrefix + "    ("
                         + WindowConfiguration.windowingModeToString(mode) + ") " + task);
             }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a55fc4e..da16de7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -36,6 +38,7 @@
 import static android.view.WindowManager.TransitionFlags;
 import static android.view.WindowManager.TransitionType;
 import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -1066,6 +1069,7 @@
                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
                 task.fillTaskInfo(tinfo);
                 change.setTaskInfo(tinfo);
+                change.setRotationAnimation(getTaskRotationAnimation(task));
             }
             out.addChange(change);
         }
@@ -1073,6 +1077,23 @@
         return out;
     }
 
+    private static int getTaskRotationAnimation(@NonNull Task task) {
+        final ActivityRecord top = task.getTopVisibleActivity();
+        if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
+        final WindowState mainWin = top.findMainWindow(false);
+        if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
+        int anim = mainWin.getRotationAnimationHint();
+        if (anim >= 0) return anim;
+        anim = mainWin.getAttrs().rotationAnimation;
+        if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
+        if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
+                || !top.matchParentBounds()) {
+            // At the moment, we only support seamless rotation if there is only one window showing.
+            return ROTATION_ANIMATION_UNSPECIFIED;
+        }
+        return mainWin.getAttrs().rotationAnimation;
+    }
+
     boolean getLegacyIsReady() {
         return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
     }
@@ -1166,6 +1187,9 @@
             final DisplayContent dc = wc.asDisplayContent();
             if (dc != null) {
                 flags |= FLAG_IS_DISPLAY;
+                if (dc.hasAlertWindowSurfaces()) {
+                    flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+                }
             }
             if (isWallpaper(wc)) {
                 flags |= FLAG_IS_WALLPAPER;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index be8b21c..f641d21 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -648,7 +648,6 @@
 
     private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
     private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
-    private EnterpriseSpecificIdCalculator mEsidCalculator;
 
     /**
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -1474,10 +1473,6 @@
             return new LockPatternUtils(mContext);
         }
 
-        EnterpriseSpecificIdCalculator newEnterpriseSpecificIdCalculator() {
-            return new EnterpriseSpecificIdCalculator(mContext);
-        }
-
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return StorageManager.isFileEncryptedNativeOnly();
         }
@@ -3126,10 +3121,6 @@
                 factoryResetIfDelayedEarlier();
 
                 ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
-
-                // This is constructed here as EnterpriseSpecificIdCalculator depends on telephony
-                // and wifi service and these services are only fully available at this stage.
-                mEsidCalculator = mInjector.newEnterpriseSpecificIdCalculator();
                 break;
         }
     }
@@ -16927,8 +16918,6 @@
     @Override
     public void setOrganizationIdForUser(
             @NonNull String callerPackage, @NonNull String organizationId, int userId) {
-        Preconditions.checkState(mEsidCalculator != null,
-                "setOrganizationIdForUser can't be called before boot phase completion");
         if (!mHasFeature) {
             return;
         }
@@ -16962,7 +16951,10 @@
                             + "be changed");
             final String dpcPackage = owner.info.getPackageName();
             mInjector.binderWithCleanCallingIdentity(() -> {
-                final String esid = mEsidCalculator.calculateEnterpriseId(dpcPackage,
+                EnterpriseSpecificIdCalculator esidCalculator =
+                        new EnterpriseSpecificIdCalculator(mContext);
+
+                final String esid = esidCalculator.calculateEnterpriseId(dpcPackage,
                         organizationId);
                 owner.mOrganizationId = organizationId;
                 owner.mEnrollmentSpecificId = esid;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 29091ce..df7f308 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -51,19 +51,13 @@
 
     EnterpriseSpecificIdCalculator(Context context) {
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
-        if (telephonyService != null) {
-            mImei = telephonyService.getImei(0);
-            mMeid = telephonyService.getMeid(0);
-        } else {
-            mImei = "";
-            mMeid = "";
-        }
+        Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
+        mImei = telephonyService.getImei(0);
+        mMeid = telephonyService.getMeid(0);
         mSerialNumber = Build.getSerial();
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        String[] macAddresses = null;
-        if (wifiManager != null) {
-            macAddresses = wifiManager.getFactoryMacAddresses();
-        }
+        Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
+        final String[] macAddresses = wifiManager.getFactoryMacAddresses();
         if (macAddresses == null || macAddresses.length == 0) {
             mMacAddress = "";
         } else {
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 988c02b..1bcc3d1 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -32,6 +32,7 @@
         "androidx.test.runner",
         "junit",
         "kotlin-test",
+        "kotlin-reflect",
         "services.core",
         "servicestests-utils",
         "truth-prebuilt",
diff --git a/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING b/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING
new file mode 100644
index 0000000..cacfcf0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "PackageManagerServiceUnitTests"
+    }
+  ]
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
new file mode 100644
index 0000000..a7644ec
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -0,0 +1,572 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
+import android.content.pm.parsing.ParsingPackage
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedAttribution
+import android.content.pm.parsing.component.ParsedComponent
+import android.content.pm.parsing.component.ParsedInstrumentation
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.parsing.component.ParsedPermission
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import android.content.pm.parsing.component.ParsedProcess
+import android.content.pm.parsing.component.ParsedProvider
+import android.content.pm.parsing.component.ParsedService
+import android.content.pm.parsing.component.ParsedUsesPermission
+import android.net.Uri
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.ArraySet
+import android.util.SparseArray
+import android.util.SparseIntArray
+import com.android.internal.R
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import java.security.KeyPairGenerator
+import java.security.PublicKey
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
+
+    override val defaultImpl = PackageImpl.forTesting("com.example.test")
+    override val creator = PackageImpl.CREATOR
+
+    override val excludedMethods = listOf(
+        // Internal methods
+        "toAppInfoToString",
+        "toAppInfoWithoutState",
+        "toAppInfoWithoutStateWithoutFlags",
+        "assignDerivedFields",
+        "buildFakeForDeletion",
+        "capPermissionPriorities",
+        "forParsing",
+        "forTesting",
+        "getBaseAppDataCredentialProtectedDirForSystemUser",
+        "getBaseAppDataDeviceProtectedDirForSystemUser",
+        "getBoolean",
+        "setBoolean",
+        "hideAsFinal",
+        "hideAsParsed",
+        "markNotActivitiesAsNotExportedIfSingleUser",
+        "sortActivities",
+        "sortReceivers",
+        "sortServices",
+        "setAllComponentsDirectBootAware",
+        // Tested through setting minor/major manually
+        "setLongVersionCode",
+        "getLongVersionCode",
+        // Tested through constructor
+        "getManifestPackageName",
+        "setManifestPackageName",
+        // Utility methods
+        "getStorageUuid",
+        // Removal not tested, irrelevant for parcelling concerns
+        "removeUsesOptionalLibrary",
+        "clearAdoptPermissions",
+        "clearOriginalPackages",
+        "clearProtectedBroadcasts",
+        "removePermission",
+        "removeUsesLibrary",
+        "removeUsesOptionalNativeLibrary",
+        // Tested manually
+        "getMimeGroups",
+        "getRequestedPermissions",
+        // Tested through asSplit
+        "asSplit",
+        "getSplitNames",
+        "getSplitCodePaths",
+        "getSplitRevisionCodes",
+        "getSplitFlags",
+        "getSplitClassLoaderNames",
+        "getSplitDependencies",
+        "setSplitCodePaths",
+        "setSplitClassLoaderName",
+        "setSplitHasCode",
+    )
+
+    override val baseParams = listOf(
+        AndroidPackage::getAppComponentFactory,
+        AndroidPackage::getAutoRevokePermissions,
+        AndroidPackage::getBackupAgentName,
+        AndroidPackage::getBanner,
+        AndroidPackage::getBaseApkPath,
+        AndroidPackage::getBaseRevisionCode,
+        AndroidPackage::getCategory,
+        AndroidPackage::getClassLoaderName,
+        AndroidPackage::getClassName,
+        AndroidPackage::getCompatibleWidthLimitDp,
+        AndroidPackage::getCompileSdkVersion,
+        AndroidPackage::getCompileSdkVersionCodeName,
+        AndroidPackage::getDataExtractionRules,
+        AndroidPackage::getDescriptionRes,
+        AndroidPackage::getFullBackupContent,
+        AndroidPackage::getGwpAsanMode,
+        AndroidPackage::getIconRes,
+        AndroidPackage::getInstallLocation,
+        AndroidPackage::getLabelRes,
+        AndroidPackage::getLargestWidthLimitDp,
+        AndroidPackage::getLogo,
+        AndroidPackage::getManageSpaceActivityName,
+        AndroidPackage::getMemtagMode,
+        AndroidPackage::getMinSdkVersion,
+        AndroidPackage::getNativeHeapZeroInitialized,
+        AndroidPackage::getNativeLibraryDir,
+        AndroidPackage::getNativeLibraryRootDir,
+        AndroidPackage::getNetworkSecurityConfigRes,
+        AndroidPackage::getNonLocalizedLabel,
+        AndroidPackage::getOverlayCategory,
+        AndroidPackage::getOverlayPriority,
+        AndroidPackage::getOverlayTarget,
+        AndroidPackage::getOverlayTargetName,
+        AndroidPackage::getPackageName,
+        AndroidPackage::getPath,
+        AndroidPackage::getPermission,
+        AndroidPackage::getPrimaryCpuAbi,
+        AndroidPackage::getProcessName,
+        AndroidPackage::getRealPackage,
+        AndroidPackage::getRequiredAccountType,
+        AndroidPackage::getRequiresSmallestWidthDp,
+        AndroidPackage::getResizeableActivity,
+        AndroidPackage::getRestrictedAccountType,
+        AndroidPackage::getRoundIconRes,
+        AndroidPackage::getSeInfo,
+        AndroidPackage::getSeInfoUser,
+        AndroidPackage::getSecondaryCpuAbi,
+        AndroidPackage::getSecondaryNativeLibraryDir,
+        AndroidPackage::getSharedUserId,
+        AndroidPackage::getSharedUserLabel,
+        AndroidPackage::getStaticSharedLibName,
+        AndroidPackage::getStaticSharedLibVersion,
+        AndroidPackage::getTargetSandboxVersion,
+        AndroidPackage::getTargetSdkVersion,
+        AndroidPackage::getTaskAffinity,
+        AndroidPackage::getTheme,
+        AndroidPackage::getUiOptions,
+        AndroidPackage::getUid,
+        AndroidPackage::getVersionName,
+        AndroidPackage::getZygotePreloadName,
+        AndroidPackage::isAllowAudioPlaybackCapture,
+        AndroidPackage::isAllowBackup,
+        AndroidPackage::isAllowClearUserData,
+        AndroidPackage::isAllowClearUserDataOnFailedRestore,
+        AndroidPackage::isAllowNativeHeapPointerTagging,
+        AndroidPackage::isAllowTaskReparenting,
+        AndroidPackage::isBackupInForeground,
+        AndroidPackage::isBaseHardwareAccelerated,
+        AndroidPackage::isCantSaveState,
+        AndroidPackage::isCoreApp,
+        AndroidPackage::isCrossProfile,
+        AndroidPackage::isDebuggable,
+        AndroidPackage::isDefaultToDeviceProtectedStorage,
+        AndroidPackage::isDirectBootAware,
+        AndroidPackage::isEnabled,
+        AndroidPackage::isExternalStorage,
+        AndroidPackage::isExtractNativeLibs,
+        AndroidPackage::isFactoryTest,
+        AndroidPackage::isForceQueryable,
+        AndroidPackage::isFullBackupOnly,
+        AndroidPackage::isGame,
+        AndroidPackage::isHasCode,
+        AndroidPackage::isHasDomainUrls,
+        AndroidPackage::isHasFragileUserData,
+        AndroidPackage::isIsolatedSplitLoading,
+        AndroidPackage::isKillAfterRestore,
+        AndroidPackage::isLargeHeap,
+        AndroidPackage::isMultiArch,
+        AndroidPackage::isNativeLibraryRootRequiresIsa,
+        AndroidPackage::isOdm,
+        AndroidPackage::isOem,
+        AndroidPackage::isOverlay,
+        AndroidPackage::isOverlayIsStatic,
+        AndroidPackage::isPartiallyDirectBootAware,
+        AndroidPackage::isPersistent,
+        AndroidPackage::isPrivileged,
+        AndroidPackage::isProduct,
+        AndroidPackage::isProfileableByShell,
+        AndroidPackage::isRequestLegacyExternalStorage,
+        AndroidPackage::isRequiredForAllUsers,
+        AndroidPackage::isResizeableActivityViaSdkVersion,
+        AndroidPackage::isRestoreAnyVersion,
+        AndroidPackage::isSignedWithPlatformKey,
+        AndroidPackage::isStaticSharedLibrary,
+        AndroidPackage::isStub,
+        AndroidPackage::isSupportsRtl,
+        AndroidPackage::isSystem,
+        AndroidPackage::isSystemExt,
+        AndroidPackage::isTestOnly,
+        AndroidPackage::isUse32BitAbi,
+        AndroidPackage::isUseEmbeddedDex,
+        AndroidPackage::isUsesCleartextTraffic,
+        AndroidPackage::isUsesNonSdkApi,
+        AndroidPackage::isVendor,
+        AndroidPackage::isVisibleToInstantApps,
+        AndroidPackage::isVmSafeMode,
+        AndroidPackage::getMaxAspectRatio,
+        AndroidPackage::getMinAspectRatio,
+        AndroidPackage::hasPreserveLegacyExternalStorage,
+        AndroidPackage::hasRequestForegroundServiceExemption,
+        AndroidPackage::hasRequestRawExternalStorageAccess,
+    )
+
+    override fun extraParams() = listOf(
+        getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+        getter(AndroidPackage::isProfileable, true),
+        getter(AndroidPackage::getVersionCode, 3),
+        getter(AndroidPackage::getVersionCodeMajor, 9),
+        getter(AndroidPackage::getUpgradeKeySets, setOf("testUpgradeKeySet")),
+        getter(AndroidPackage::isAnyDensity, false, 0),
+        getter(AndroidPackage::isResizeable, false, 0),
+        getter(AndroidPackage::isSupportsSmallScreens, false, 0),
+        getter(AndroidPackage::isSupportsNormalScreens, false, 0),
+        getter(AndroidPackage::isSupportsLargeScreens, false, 0),
+        getter(AndroidPackage::isSupportsExtraLargeScreens, false, 0),
+        adder(AndroidPackage::getAdoptPermissions, "test.adopt.PERMISSION"),
+        adder(AndroidPackage::getOriginalPackages, "com.test.original"),
+        adder(AndroidPackage::getImplicitPermissions, "test.implicit.PERMISSION"),
+        adder(AndroidPackage::getLibraryNames, "testLibraryName"),
+        adder(AndroidPackage::getProtectedBroadcasts, "test.protected.BROADCAST"),
+        adder(AndroidPackage::getQueriesPackages, "com.test.package.queries"),
+        adder(AndroidPackage::getQueriesProviders, "com.test.package.queries.provider"),
+        adder(AndroidPackage::getUsesLibraries, "testUsesLibrary"),
+        adder(AndroidPackage::getUsesNativeLibraries, "testUsesNativeLibrary"),
+        adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
+        adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
+        adder(AndroidPackage::getUsesStaticLibraries, "testUsesStaticLibrary"),
+        getSetByValue(
+            AndroidPackage::getUsesStaticLibrariesVersions,
+            PackageImpl::addUsesStaticLibraryVersion,
+            (testCounter++).toLong(),
+            transformGet = { it?.singleOrNull() }
+        ),
+        getSetByValue(
+            AndroidPackage::areAttributionsUserVisible,
+            ParsingPackage::setAttributionsAreUserVisible,
+            true
+        ),
+        getSetByValue2(
+            AndroidPackage::getOverlayables,
+            PackageImpl::addOverlayable,
+            "testOverlayableName" to "testActorName",
+            transformGet = { "testOverlayableName" to it["testOverlayableName"] }
+        ),
+        getSetByValue(
+            AndroidPackage::getMetaData,
+            PackageImpl::setMetaData,
+            "testBundleKey" to "testBundleValue",
+            transformGet = { "testBundleKey" to it?.getString("testBundleKey") },
+            transformSet = { Bundle().apply { putString(it.first, it.second) } }
+        ),
+        getSetByValue(
+            AndroidPackage::getAttributions,
+            PackageImpl::addAttribution,
+            Triple("testTag", 13, listOf("testInherit")),
+            transformGet = { it.singleOrNull()?.let { Triple(it.tag, it.label, it.inheritFrom) } },
+            transformSet = { it?.let { ParsedAttribution(it.first, it.second, it.third) } }
+        ),
+        getSetByValue2(
+            AndroidPackage::getKeySetMapping,
+            PackageImpl::addKeySet,
+            "testKeySetName" to testKey(),
+            transformGet = { "testKeySetName" to it["testKeySetName"]?.singleOrNull() },
+        ),
+        getSetByValue(
+            AndroidPackage::getPermissionGroups,
+            PackageImpl::addPermissionGroup,
+            "test.permission.GROUP",
+            transformGet = { it.singleOrNull()?.name },
+            transformSet = { ParsedPermissionGroup().apply { setName(it) } }
+        ),
+        getSetByValue2(
+            AndroidPackage::getPreferredActivityFilters,
+            PackageImpl::addPreferredActivityFilter,
+            "TestClassName" to ParsedIntentInfo().apply {
+                addDataScheme("http")
+                addDataAuthority("test.pm.server.android.com", null)
+            },
+            transformGet = { it.singleOrNull()?.let { it.first to it.second } },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.first },
+                    { it.second.schemesIterator().asSequence().singleOrNull() },
+                    { it.second.authoritiesIterator().asSequence().singleOrNull()?.host },
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getQueriesIntents,
+            PackageImpl::addQueriesIntent,
+            Intent(Intent.ACTION_VIEW, Uri.parse("https://test.pm.server.android.com")),
+            transformGet = { it.singleOrNull() },
+            compare = { first, second -> first?.filterEquals(second) },
+        ),
+        getSetByValue(
+            AndroidPackage::getRestrictUpdateHash,
+            PackageImpl::setRestrictUpdateHash,
+            byteArrayOf(0, 1, 2, 3, 4),
+            compare = ByteArray::contentEquals
+        ),
+        getSetByValue(
+            AndroidPackage::getSigningDetails,
+            PackageImpl::setSigningDetails,
+            testKey(),
+            transformGet = { it.publicKeys?.takeIf { it.size > 0 }?.valueAt(0) },
+            transformSet = {
+                SigningDetails(
+                    null,
+                    SigningDetails.SignatureSchemeVersion.UNKNOWN,
+                    ArraySet<PublicKey>().apply { add(it) },
+                    null
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getUsesStaticLibrariesCertDigests,
+            PackageImpl::addUsesStaticLibraryCertDigests,
+            arrayOf("testCertDigest"),
+            transformGet = { it?.singleOrNull() },
+            compare = Array<String?>?::contentEquals
+        ),
+        getSetByValue(
+            AndroidPackage::getActivities,
+            PackageImpl::addActivity,
+            "TestActivityName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+        ),
+        getSetByValue(
+            AndroidPackage::getReceivers,
+            PackageImpl::addReceiver,
+            "TestReceiverName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedActivity().apply { name = it }.withMimeGroups() }
+        ),
+        getSetByValue(
+            AndroidPackage::getServices,
+            PackageImpl::addService,
+            "TestServiceName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedService().apply { name = it }.withMimeGroups() }
+        ),
+        getSetByValue(
+            AndroidPackage::getProviders,
+            PackageImpl::addProvider,
+            "TestProviderName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedProvider().apply { name = it }.withMimeGroups() }
+        ),
+        getSetByValue(
+            AndroidPackage::getInstrumentations,
+            PackageImpl::addInstrumentation,
+            "TestInstrumentationName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedInstrumentation().apply { name = it } }
+        ),
+        getSetByValue(
+            AndroidPackage::getConfigPreferences,
+            PackageImpl::addConfigPreference,
+            testCounter++,
+            transformGet = { it.singleOrNull()?.reqGlEsVersion ?: -1 },
+            transformSet = { ConfigurationInfo().apply { reqGlEsVersion = it } }
+        ),
+        getSetByValue(
+            AndroidPackage::getFeatureGroups,
+            PackageImpl::addFeatureGroup,
+            "test.feature.GROUP",
+            transformGet = { it.singleOrNull()?.features?.singleOrNull()?.name.orEmpty() },
+            transformSet = {
+                FeatureGroupInfo().apply {
+                    features = arrayOf(FeatureInfo().apply { name = it })
+                }
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getPermissions,
+            PackageImpl::addPermission,
+            "test.PERMISSION",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedPermission().apply { name = it } }
+        ),
+        getSetByValue(
+            AndroidPackage::getUsesPermissions,
+            PackageImpl::addUsesPermission,
+            "test.USES_PERMISSION",
+            transformGet = {
+                // Need to strip implicit permission, which calls addUsesPermission when added
+                it.filterNot { it.name == "test.implicit.PERMISSION" }
+                    .singleOrNull()?.name.orEmpty()
+            },
+            transformSet = { ParsedUsesPermission(it, 0) }
+        ),
+        getSetByValue(
+            AndroidPackage::getReqFeatures,
+            PackageImpl::addReqFeature,
+            "test.feature.INFO",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { FeatureInfo().apply { name = it } }
+        ),
+        getSetByValue(
+            AndroidPackage::getMinExtensionVersions,
+            PackageImpl::setMinExtensionVersions,
+            SparseIntArray().apply { put(testCounter++, testCounter++) },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.size() },
+                    { it.keyAt(0) },
+                    { it.valueAt(0) },
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getProcesses,
+            PackageImpl::setProcesses,
+            mapOf("testProcess" to ParsedProcess().apply { name = "testProcessName" }),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it["testProcess"]?.name },
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getProperties,
+            PackageImpl::addProperty,
+            PackageManager.Property(
+                "testPropertyName",
+                "testPropertyValue",
+                "testPropertyClassName",
+                "testPropertyPackageName"
+            ),
+            transformGet = { it["testPropertyName"] },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    PackageManager.Property::getName,
+                    PackageManager.Property::getClassName,
+                    PackageManager.Property::getPackageName,
+                    PackageManager.Property::getString,
+                )
+            }
+        ),
+    )
+
+    override fun initialObject() = PackageImpl.forParsing(
+        "com.example.test",
+        "/test/test/base.apk",
+        "/test/test",
+        mockThrowOnUnmocked {
+            whenever(getInteger(R.styleable.AndroidManifest_revisionCode, 0)) { 4 }
+            whenever(getBoolean(R.styleable.AndroidManifest_isolatedSplits, false)) { true }
+
+            // Return invalid values here so that the getter/setter is tested properly
+            whenever(getInteger(R.styleable.AndroidManifest_versionCode, 0)) { -1 }
+            whenever(getInteger(R.styleable.AndroidManifest_versionCodeMajor, 0)) { -1 }
+            whenever(
+                getNonConfigurationString(
+                    R.styleable.AndroidManifest_versionName,
+                    0
+                )
+            ) { "" }
+            whenever(getInteger(R.styleable.AndroidManifest_compileSdkVersion, 0)) { 31 }
+            whenever(
+                getNonConfigurationString(
+                    R.styleable.AndroidManifest_compileSdkVersionCodename,
+                    0
+                )
+            ) { "" }
+        },
+        true
+    )
+        .asSplit(
+            arrayOf("testSplitNameZero", "testSplitNameOne"),
+            arrayOf("/test/testSplitZero.apk", "/test/testSplitOne.apk"),
+            intArrayOf(10, 11),
+            SparseArray<IntArray>().apply {
+                put(0, intArrayOf(-1))
+                put(1, intArrayOf(0))
+            }
+        )
+        .setSplitHasCode(0, true)
+        .setSplitHasCode(1, false)
+        .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
+        .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
+
+    override fun extraAssertions(before: Parcelable, after: Parcelable) {
+        super.extraAssertions(before, after)
+        after as PackageImpl
+        expect.that(after.manifestPackageName).isEqualTo("com.example.test")
+        expect.that(after.isCoreApp).isTrue()
+        expect.that(after.isIsolatedSplitLoading).isEqualTo(true)
+        expect.that(after.longVersionCode).isEqualTo(38654705667)
+        expect.that(after.requestedPermissions)
+            .containsExactlyElementsIn(after.usesPermissions.map { it.name })
+            .inOrder()
+
+        expect.that(after.mimeGroups).containsExactly(
+            "TestActivityName/mimeGroup",
+            "TestReceiverName/mimeGroup",
+            "TestServiceName/mimeGroup",
+            "TestProviderName/mimeGroup"
+        )
+
+        expect.that(after.splitNames).asList()
+            .containsExactly("testSplitNameZero", "testSplitNameOne")
+            .inOrder()
+        expect.that(after.splitCodePaths).asList()
+            .containsExactly("/test/testSplitZero.apk", "/test/testSplitOne.apk")
+            .inOrder()
+        expect.that(after.splitRevisionCodes).asList()
+            .containsExactly(10, 11)
+            .inOrder()
+        expect.that(after.splitFlags).asList()
+            .containsExactly(ApplicationInfo.FLAG_HAS_CODE, 0)
+            .inOrder()
+        expect.that(after.splitClassLoaderNames).asList()
+            .containsExactly("testSplitClassLoaderNameZero", "testSplitClassLoaderNameOne")
+            .inOrder()
+
+        expect.that(after.splitDependencies).isNotNull()
+        after.splitDependencies?.let {
+            expect.that(it.size()).isEqualTo(2)
+            expect.that(it.get(0)).asList().containsExactly(-1)
+            expect.that(it.get(1)).asList().containsExactly(0)
+        }
+    }
+
+    private fun testKey() = KeyPairGenerator.getInstance("RSA")
+        .generateKeyPair()
+        .public
+
+    private fun <T : ParsedComponent> T.withMimeGroups() = apply {
+        val componentName = name
+        addIntent(ParsedIntentInfo().apply {
+            addMimeGroup("$componentName/mimeGroup")
+        })
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
new file mode 100644
index 0000000..e16a187
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
@@ -0,0 +1,413 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.util.IgnoreableExpect
+import com.google.common.truth.Expect
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import java.util.Objects
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.KFunction1
+import kotlin.reflect.KFunction2
+import kotlin.reflect.KFunction3
+import kotlin.reflect.KVisibility
+import kotlin.reflect.full.allSuperclasses
+import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.isSubclassOf
+import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.staticProperties
+import kotlin.reflect.jvm.jvmErasure
+
+
+@ExperimentalContracts
+abstract class ParcelableComponentTest(
+    private val getterType: KClass<*>,
+    private val setterType: KClass<out Parcelable>
+) {
+
+    companion object {
+        private val DEFAULT_EXCLUDED = listOf(
+            // Java
+            "toString",
+            "equals",
+            "hashCode",
+            // Parcelable
+            "getStability",
+            "describeContents",
+            "writeToParcel",
+            // @DataClass
+            "__metadata"
+        )
+    }
+
+    internal val ignoreableExpect = IgnoreableExpect()
+
+    // Hides internal type
+    @get:Rule
+    val ignoreableAsTestRule: TestRule = ignoreableExpect
+
+    val expect: Expect
+        get() = ignoreableExpect.expect
+
+    protected var testCounter = 1
+
+    protected abstract val defaultImpl: Any
+    protected abstract val creator: Parcelable.Creator<out Parcelable>
+
+    protected open val excludedMethods: Collection<String> = emptyList()
+
+    protected abstract val baseParams: Collection<KFunction1<*, Any?>>
+
+    private val getters = getterType.memberFunctions
+        .filterNot { DEFAULT_EXCLUDED.contains(it.name) }
+
+    private val setters = setterType.memberFunctions
+        .filterNot { DEFAULT_EXCLUDED.contains(it.name) }
+
+    constructor(kClass: KClass<out Parcelable>) : this(kClass, kClass)
+
+    @Before
+    fun checkNoPublicFields() {
+        // Fields are not currently testable, and the idea is to enforce interface access for
+        // immutability purposes, so disallow any public fields from existing.
+        expect.that(getterType.memberProperties.filter { it.visibility == KVisibility.PUBLIC }
+            .filterNot { DEFAULT_EXCLUDED.contains(it.name) })
+            .isEmpty()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun <ObjectType, ReturnType> buildParams(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+    ): Param? {
+        return buildParams<ObjectType, ReturnType, ReturnType, ReturnType>(
+            getFunction,
+            autoValue(getFunction) as ReturnType ?: return null
+        )
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> buildParams(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        value: SetType,
+    ): Param? {
+        return getSetByValue<ObjectType, ReturnType, SetType, Any?>(
+            getFunction,
+            findSetFunction(getFunction) ?: return null,
+            value
+        )
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun <ObjectType, ReturnType> findSetFunction(
+        getFunction: KFunction1<ObjectType, ReturnType>
+    ): KFunction2<ObjectType, ReturnType, Any?>? {
+        val getFunctionName = getFunction.name
+        val prefix = when {
+            getFunctionName.startsWith("get") -> "get"
+            getFunctionName.startsWith("is") -> "is"
+            getFunctionName.startsWith("has") -> "has"
+            else -> throw IllegalArgumentException("Unsupported method name $getFunctionName")
+        }
+        val setFunctionName = "set" + getFunctionName.removePrefix(prefix)
+        val setFunction = setters.filter { it.name == setFunctionName }
+            .minByOrNull { it.parameters.size }
+
+        if (setFunction == null) {
+            expect.withMessage("$getFunctionName does not have corresponding $setFunctionName")
+                .fail()
+            return null
+        }
+
+        return setFunction as KFunction2<ObjectType, ReturnType, Any?>
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun <ObjectType, ReturnType, SetType> findAddFunction(
+        getFunction: KFunction1<ObjectType, ReturnType>
+    ): KFunction2<ObjectType, SetType, Any?>? {
+        val getFunctionName = getFunction.name
+        if (!getFunctionName.startsWith("get")) {
+            throw IllegalArgumentException("Unsupported method name $getFunctionName")
+        }
+
+        val setFunctionName = "add" + getFunctionName.removePrefix("get").run {
+            // Remove plurality
+            when {
+                endsWith("ies") -> "${removeSuffix("ies")}y"
+                endsWith("s") -> removeSuffix("s")
+                else -> this
+            }
+        }
+
+        val setFunction = setters.filter { it.name == setFunctionName }
+            .minByOrNull { it.parameters.size }
+
+        if (setFunction == null) {
+            expect.withMessage("$getFunctionName does not have corresponding $setFunctionName")
+                .fail()
+            return null
+        }
+
+        return setFunction as KFunction2<ObjectType, SetType, Any?>
+    }
+
+    protected fun <ObjectType, ReturnType> getter(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        valueToSet: ReturnType
+    ) = buildParams<ObjectType, ReturnType, ReturnType, ReturnType>(getFunction, valueToSet)
+
+    protected fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> getter(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        expectedValue: CompareType,
+        valueToSet: SetType
+    ): Param? {
+        return getSetByValue(
+            getFunction,
+            findSetFunction(getFunction) ?: return null,
+            value = expectedValue,
+            transformSet = { valueToSet }
+        )
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    protected fun <ObjectType, ReturnType> adder(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        value: ReturnType,
+    ): Param? {
+        return getSetByValue(
+            getFunction,
+            findAddFunction<ObjectType, Any?, ReturnType>(getFunction) ?: return null,
+            value,
+            transformGet = {
+                // Primitive arrays don't implement Iterable, so cast manually
+                when (it) {
+                    is BooleanArray -> it.singleOrNull()
+                    is IntArray -> it.singleOrNull()
+                    is LongArray -> it.singleOrNull()
+                    is Iterable<*> -> it.singleOrNull()
+                    else -> null
+                }
+            },
+        )
+    }
+
+    /**
+     * Method to provide custom getter and setter logic for values which are not simple primitives
+     * or cannot be directly compared using [Objects.equals].
+     *
+     * @param getFunction the getter function which will be called and marked as tested
+     * @param setFunction the setter function which will be called and marked as tested
+     * @param value the value for comparison through the parcel-unparcel cycle, which can be
+     *          anything, like the [String] ID of an inner object
+     * @param transformGet the function to transform the result of [getFunction] into [value]
+     * @param transformSet the function to transform [value] into an input for [setFunction]
+     * @param compare the function that compares the pre/post-parcel [value] objects
+     */
+    @Suppress("UNCHECKED_CAST")
+    protected fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> getSetByValue(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        setFunction: KFunction2<ObjectType, SetType, Any?>,
+        value: CompareType,
+        transformGet: (ReturnType) -> CompareType = { it as CompareType },
+        transformSet: (CompareType) -> SetType = { it as SetType },
+        compare: (CompareType, CompareType) -> Boolean? = Objects::equals
+    ) = Param(
+        getFunction.name,
+        { transformGet(getFunction.call(it as ObjectType)) },
+        setFunction.name,
+        { setFunction.call(it.first() as ObjectType, transformSet(it[1] as CompareType)) },
+        { value },
+        { first, second -> compare(first as CompareType, second as CompareType) == true }
+    )
+
+    /**
+     * Variant of [getSetByValue] that allows specifying a [setFunction] with 2 inputs.
+     */
+    @Suppress("UNCHECKED_CAST")
+    protected fun <ObjectType, ReturnType, SetType1 : Any?, SetType2 : Any?, CompareType : Any?>
+            getSetByValue2(
+        getFunction: KFunction1<ObjectType, ReturnType>,
+        setFunction: KFunction3<ObjectType, SetType1, SetType2, Any>,
+        value: CompareType,
+        transformGet: (ReturnType) -> CompareType = { it as CompareType },
+        transformSet: (CompareType) -> Pair<SetType1, SetType2> =
+            { it as Pair<SetType1, SetType2> },
+        compare: (CompareType, CompareType) -> Boolean = Objects::equals
+    ) = Param(
+        getFunction.name,
+        { transformGet(getFunction.call(it as ObjectType)) },
+        setFunction.name,
+        {
+            val pair = transformSet(it[1] as CompareType)
+            setFunction.call(it.first() as ObjectType, pair.first, pair.second)
+        },
+        { value },
+        { first, second -> compare(first as CompareType, second as CompareType) }
+    )
+
+    protected fun autoValue(getFunction: KFunction<*>) = when (getFunction.returnType.jvmErasure) {
+        Boolean::class -> (getFunction.call(defaultImpl) as Boolean?)?.not() ?: true
+        CharSequence::class,
+        String::class -> getFunction.name + "TEST"
+        Int::class -> testCounter++
+        Long::class -> (testCounter++).toLong()
+        Float::class -> (testCounter++).toFloat()
+        else -> {
+            expect.withMessage("${getFunction.name} needs to provide value").fail()
+            null
+        }
+    }
+
+    /**
+     * Verifies two instances are equivalent via a series of properties. For use when a public API
+     * class has not implemented equals.
+     */
+    @Suppress("UNCHECKED_CAST")
+    protected fun <T : Any> equalBy(
+        first: T?,
+        second: T?,
+        vararg properties: (T) -> Any?
+    ) = properties.all { property ->
+        first?.let { property(it) } == second?.let { property(it) }
+    }
+
+    @Test
+    fun valueComparison() {
+        val params = baseParams.mapNotNull(::buildParams) + extraParams().filterNotNull()
+        val before = initialObject()
+
+        params.forEach { it.setFunction(arrayOf(before, it.value())) }
+
+        val parcel = Parcel.obtain()
+        writeToParcel(parcel, before)
+
+        val dataSize = parcel.dataSize()
+
+        parcel.setDataPosition(0)
+
+        val after = creator.createFromParcel(parcel)
+
+        expect.withMessage("Mismatched write and read data sizes")
+            .that(parcel.dataPosition())
+            .isEqualTo(dataSize)
+
+        parcel.recycle()
+
+        runAssertions(params, before, after)
+    }
+
+    @Test
+    open fun parcellingSize() {
+        val parcelOne = Parcel.obtain()
+        writeToParcel(parcelOne, initialObject())
+
+        val parcelTwo = Parcel.obtain()
+        initialObject().writeToParcel(parcelTwo, 0)
+
+        val superDataSizes = setterType.allSuperclasses
+            .filter { it.isSubclassOf(Parcelable::class) }
+            .mapNotNull { it.memberFunctions.find { it.name == "writeToParcel" } }
+            .filter { it.isFinal }
+            .map {
+                val parcel = Parcel.obtain()
+                initialObject().writeToParcel(parcel, 0)
+                parcel.dataSize().also { parcel.recycle() }
+            }
+
+        if ((superDataSizes + parcelOne.dataSize() + parcelTwo.dataSize()).distinct().size != 1) {
+            listOf(getterType, setterType).distinct().forEach {
+                val creatorProperties = it.staticProperties.filter { it.name == "CREATOR" }
+                if (creatorProperties.size > 1) {
+                    expect.withMessage(
+                        "Multiple matching CREATOR fields found for" +
+                                it.qualifiedName
+                    )
+                        .that(creatorProperties)
+                        .hasSize(1)
+                } else {
+                    val creator = creatorProperties.single().get()
+                    if (creator !is Parcelable.Creator<*>) {
+                        expect.that(creator).isInstanceOf(Parcelable.Creator::class.java)
+                        return
+                    }
+
+                    parcelTwo.setDataPosition(0)
+                    val parcelable = creator.createFromParcel(parcelTwo)
+                    if (parcelable::class.isSubclassOf(setterType)) {
+                        expect.withMessage(
+                            "${it.qualifiedName} which does not safely override writeToParcel " +
+                                    "cannot contain a subclass CREATOR field"
+                        )
+                            .fail()
+                    }
+                }
+            }
+        }
+
+        parcelOne.recycle()
+        parcelTwo.recycle()
+    }
+
+    private fun runAssertions(params: List<Param>, before: Parcelable, after: Parcelable) {
+        params.forEach {
+            val actual = it.getFunction(after)
+            val expected = it.value()
+            val equal = it.compare(actual, expected)
+            expect.withMessage("${it.getFunctionName} was $actual, expected $expected")
+                .that(equal)
+                .isTrue()
+        }
+
+        extraAssertions(before, after)
+
+        // TODO: Handle method overloads?
+        val expectedFunctions = (getters.map { it.name }
+                + setters.map { it.name }
+                - excludedMethods)
+            .distinct()
+
+        val allTestedFunctions = params.flatMap {
+            listOfNotNull(it.getFunctionName, it.setFunctionName)
+        }
+        expect.that(allTestedFunctions).containsExactlyElementsIn(expectedFunctions)
+    }
+
+    open fun extraParams(): Collection<Param?> = emptyList()
+
+    open fun initialObject(): Parcelable = setterType.createInstance()
+
+    open fun extraAssertions(before: Parcelable, after: Parcelable) {}
+
+    open fun writeToParcel(parcel: Parcel, value: Parcelable) = value.writeToParcel(parcel, 0)
+
+    data class Param(
+        val getFunctionName: String,
+        val getFunction: (Any?) -> Any?,
+        val setFunctionName: String?,
+        val setFunction: (Array<Any?>) -> Unit,
+        val value: () -> Any?,
+        val compare: (Any?, Any?) -> Boolean = Objects::equals
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.kt
new file mode 100644
index 0000000..d506190
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorInvalidTest.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.server.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.parsing.parcelling.java.TestSubWithCreator
+import com.android.server.pm.test.parsing.parcelling.java.TestSuperClass
+import org.junit.Test
+import kotlin.contracts.ExperimentalContracts
+
+/**
+ * Verifies the failing side of [ParcelableCreatorValidTest]. The sole difference is the addition
+ * of [TestSubWithCreator.CREATOR].
+ */
+@ExperimentalContracts
+class ParcelableCreatorInvalidTest :
+    ParcelableComponentTest(TestSuperClass::class, TestSubWithCreator::class) {
+
+    override val defaultImpl = TestSubWithCreator()
+
+    override val creator = object : Parcelable.Creator<Parcelable> {
+        override fun createFromParcel(source: Parcel) = TestSubWithCreator(source)
+        override fun newArray(size: Int) = Array<TestSubWithCreator?>(size) { null }
+    }
+
+    override val excludedMethods = listOf("writeSubToParcel")
+
+    override val baseParams = listOf(TestSuperClass::getSuperString)
+
+    override fun writeToParcel(parcel: Parcel, value: Parcelable) {
+        (value as TestSubWithCreator).writeSubToParcel(parcel, 0)
+    }
+
+    @Test
+    override fun parcellingSize() {
+        super.parcellingSize()
+        if (expect.hasFailures()) {
+            // This is a hack to ignore an expected failure result. Doing it this way, rather than
+            // adding a switch in the test itself, prevents it from accidentally passing through a
+            // programming error.
+            ignoreableExpect.ignore()
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt
new file mode 100644
index 0000000..f1bc7b5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableCreatorValidTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.server.pm.test.parsing.parcelling.java.TestSubWithoutCreator
+import com.android.server.pm.test.parsing.parcelling.java.TestSuperClass
+import kotlin.contracts.ExperimentalContracts
+
+/**
+ * Tests the [Parcelable] CREATOR verification by using a mock object with known differences to
+ * ensure that the method succeeds/fails.
+ */
+@ExperimentalContracts
+class ParcelableCreatorValidTest :
+    ParcelableComponentTest(TestSuperClass::class, TestSubWithoutCreator::class) {
+
+    override val defaultImpl = TestSubWithoutCreator()
+
+    override val creator = object : Parcelable.Creator<Parcelable> {
+        override fun createFromParcel(source: Parcel) = TestSubWithoutCreator(source)
+        override fun newArray(size: Int) = Array<TestSubWithoutCreator?>(size) { null }
+    }
+
+    override val excludedMethods = listOf("writeSubToParcel")
+
+    override val baseParams = listOf(TestSuperClass::getSuperString)
+
+    override fun writeToParcel(parcel: Parcel, value: Parcelable) {
+        (value as TestSubWithoutCreator).writeSubToParcel(parcel, 0)
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
new file mode 100644
index 0000000..ece600b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.ActivityInfo
+import android.content.pm.parsing.component.ParsedActivity
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedActivityTest : ParsedMainComponentTest(ParsedActivity::class) {
+
+    override val defaultImpl = ParsedActivity()
+    override val creator = ParsedActivity.CREATOR
+
+    override val mainComponentSubclassBaseParams = listOf(
+        ParsedActivity::getPermission,
+        ParsedActivity::getColorMode,
+        ParsedActivity::getConfigChanges,
+        ParsedActivity::getDocumentLaunchMode,
+        ParsedActivity::getLaunchMode,
+        ParsedActivity::getLockTaskLaunchMode,
+        ParsedActivity::getMaxAspectRatio,
+        ParsedActivity::getMaxRecents,
+        ParsedActivity::getMinAspectRatio,
+        ParsedActivity::getParentActivityName,
+        ParsedActivity::getPersistableMode,
+        ParsedActivity::getPrivateFlags,
+        ParsedActivity::getRequestedVrComponent,
+        ParsedActivity::getResizeMode,
+        ParsedActivity::getRotationAnimation,
+        ParsedActivity::getScreenOrientation,
+        ParsedActivity::getSoftInputMode,
+        ParsedActivity::getTargetActivity,
+        ParsedActivity::getTaskAffinity,
+        ParsedActivity::getTheme,
+        ParsedActivity::getUiOptions,
+        ParsedActivity::isSupportsSizeChanges,
+    )
+
+    override fun mainComponentSubclassExtraParams() = listOf(
+        getSetByValue(
+            ParsedActivity::getWindowLayout,
+            ParsedActivity::setWindowLayout,
+            ActivityInfo.WindowLayout(1, 1f, 2, 1f, 3, 4, 5),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    ActivityInfo.WindowLayout::width,
+                    ActivityInfo.WindowLayout::widthFraction,
+                    ActivityInfo.WindowLayout::height,
+                    ActivityInfo.WindowLayout::heightFraction,
+                    ActivityInfo.WindowLayout::gravity,
+                    ActivityInfo.WindowLayout::minWidth,
+                    ActivityInfo.WindowLayout::minHeight
+                )
+            }
+        )
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
new file mode 100644
index 0000000..e739dc7
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.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.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedAttribution
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class) {
+
+    override val defaultImpl = ParsedAttribution("", 0, emptyList())
+    override val creator = ParsedAttribution.CREATOR
+
+    override val baseParams = listOf(
+        ParsedAttribution::getTag,
+        ParsedAttribution::getLabel,
+    )
+
+    override fun extraParams() = listOf(
+        getter(ParsedAttribution::getInheritFrom, listOf("testInheritFrom"))
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
new file mode 100644
index 0000000..0a22f6d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.PackageManager
+import android.content.pm.parsing.component.ParsedComponent
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Bundle
+import android.os.Parcelable
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction1
+
+@ExperimentalContracts
+abstract class ParsedComponentTest(kClass: KClass<out Parcelable>) :
+    ParcelableComponentTest(kClass) {
+
+    final override val excludedMethods
+        get() = subclassExcludedMethods + listOf(
+            // Method aliases/utilities
+            "getClassName",
+            "getComponentName",
+            "setProperties" // Tested though addProperty
+        )
+
+    open val subclassExcludedMethods: Collection<String> = emptyList()
+
+    final override val baseParams
+        get() = subclassBaseParams + listOf(
+            ParsedComponent::getBanner,
+            ParsedComponent::getDescriptionRes,
+            ParsedComponent::getFlags,
+            ParsedComponent::getIcon,
+            ParsedComponent::getLabelRes,
+            ParsedComponent::getLogo,
+            ParsedComponent::getName,
+            ParsedComponent::getNonLocalizedLabel,
+            ParsedComponent::getPackageName,
+        )
+
+    abstract val subclassBaseParams: Collection<KFunction1<*, Any?>>
+
+    final override fun extraParams() = subclassExtraParams() + listOf(
+        getSetByValue(
+            ParsedComponent::getIntents,
+            ParsedComponent::addIntent,
+            "TestLabel",
+            transformGet = { it.singleOrNull()?.nonLocalizedLabel },
+            transformSet = { ParsedIntentInfo().setNonLocalizedLabel(it) },
+        ),
+        getSetByValue(
+            ParsedComponent::getProperties,
+            ParsedComponent::addProperty,
+            PackageManager.Property(
+                "testPropertyName",
+                "testPropertyValue",
+                "testPropertyClassName",
+                "testPropertyPackageName"
+            ),
+            transformGet = { it["testPropertyName"] },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    PackageManager.Property::getName,
+                    PackageManager.Property::getClassName,
+                    PackageManager.Property::getPackageName,
+                    PackageManager.Property::getString,
+                )
+            }
+        ),
+        getSetByValue(
+            ParsedComponent::getMetaData,
+            ParsedComponent::setMetaData,
+            "testBundleKey" to "testBundleValue",
+            transformGet = { "testBundleKey" to it?.getString("testBundleKey") },
+            transformSet = { Bundle().apply { putString(it.first, it.second) } }
+        ),
+    )
+
+    open fun subclassExtraParams(): Collection<Param?> = emptyList()
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
new file mode 100644
index 0000000..b7a85cc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedInstrumentation
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedInstrumentationTest : ParsedComponentTest(ParsedInstrumentation::class) {
+
+    override val defaultImpl = ParsedInstrumentation()
+    override val creator = ParsedInstrumentation.CREATOR
+
+    override val subclassBaseParams = listOf(
+        ParsedInstrumentation::getTargetPackage,
+        ParsedInstrumentation::getTargetProcesses,
+        ParsedInstrumentation::isFunctionalTest,
+        ParsedInstrumentation::isHandleProfiling,
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
new file mode 100644
index 0000000..e27bdf2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.PatternMatcher
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedIntentInfoTest : ParcelableComponentTest(ParsedIntentInfo::class) {
+
+    override val defaultImpl = ParsedIntentInfo()
+
+    override val creator = object : Parcelable.Creator<ParsedIntentInfo> {
+        override fun createFromParcel(source: Parcel) = ParsedIntentInfo(source)
+        override fun newArray(size: Int) = Array<ParsedIntentInfo?>(size) { null }
+    }
+
+    override val excludedMethods = listOf(
+        // Used to parcel
+        "writeIntentInfoToParcel",
+        // All remaining IntentFilter methods, which are out of scope
+        "hasDataPath",
+        "hasDataSchemeSpecificPart",
+        "matchAction",
+        "matchData",
+        "actionsIterator",
+        "addAction",
+        "addCategory",
+        "addDataAuthority",
+        "addDataPath",
+        "addDataScheme",
+        "addDataSchemeSpecificPart",
+        "addDataType",
+        "addDynamicDataType",
+        "addMimeGroup",
+        "asPredicate",
+        "asPredicateWithTypeResolution",
+        "authoritiesIterator",
+        "categoriesIterator",
+        "clearDynamicDataTypes",
+        "countActions",
+        "countCategories",
+        "countDataAuthorities",
+        "countDataPaths",
+        "countDataSchemeSpecificParts",
+        "countDataSchemes",
+        "countDataTypes",
+        "countMimeGroups",
+        "countStaticDataTypes",
+        "dataTypes",
+        "debugCheck",
+        "dump",
+        "dumpDebug",
+        "getAction",
+        "getAutoVerify",
+        "getCategory",
+        "getDataAuthority",
+        "getDataPath",
+        "getDataScheme",
+        "getDataSchemeSpecificPart",
+        "getDataType",
+        "getHosts",
+        "getHostsList",
+        "getMimeGroup",
+        "getOrder",
+        "getPriority",
+        "getVisibilityToInstantApp",
+        "handleAllWebDataURI",
+        "handlesWebUris",
+        "hasAction",
+        "hasCategory",
+        "hasDataAuthority",
+        "hasDataScheme",
+        "hasDataType",
+        "hasExactDataType",
+        "hasExactDynamicDataType",
+        "hasExactStaticDataType",
+        "hasMimeGroup",
+        "isExplicitlyVisibleToInstantApp",
+        "isImplicitlyVisibleToInstantApp",
+        "isVerified",
+        "isVisibleToInstantApp",
+        "match",
+        "matchCategories",
+        "matchDataAuthority",
+        "mimeGroupsIterator",
+        "needsVerification",
+        "pathsIterator",
+        "readFromXml",
+        "schemeSpecificPartsIterator",
+        "schemesIterator",
+        "setAutoVerify",
+        "setOrder",
+        "setPriority",
+        "setVerified",
+        "setVisibilityToInstantApp",
+        "typesIterator",
+        "writeToXml",
+    )
+
+    override val baseParams = listOf(
+        ParsedIntentInfo::getIcon,
+        ParsedIntentInfo::getLabelRes,
+        ParsedIntentInfo::isHasDefault,
+        ParsedIntentInfo::getNonLocalizedLabel,
+    )
+
+    override fun initialObject() = ParsedIntentInfo().apply {
+        addAction("test.ACTION")
+        addDataAuthority("testAuthority", "404")
+        addCategory("test.CATEGORY")
+        addMimeGroup("testMime")
+        addDataPath("testPath", PatternMatcher.PATTERN_LITERAL)
+    }
+
+    override fun extraAssertions(before: Parcelable, after: Parcelable) {
+        super.extraAssertions(before, after)
+        after as ParsedIntentInfo
+        expect.that(after.actionsIterator().asSequence().singleOrNull())
+            .isEqualTo("test.ACTION")
+
+        val authority = after.authoritiesIterator().asSequence().singleOrNull()
+        expect.that(authority?.host).isEqualTo("testAuthority")
+        expect.that(authority?.port).isEqualTo(404)
+
+        expect.that(after.categoriesIterator().asSequence().singleOrNull())
+            .isEqualTo("test.CATEGORY")
+        expect.that(after.mimeGroupsIterator().asSequence().singleOrNull())
+            .isEqualTo("testMime")
+        expect.that(after.hasDataPath("testPath")).isTrue()
+    }
+
+    override fun writeToParcel(parcel: Parcel, value: Parcelable) =
+        ParsedIntentInfo.PARCELER.parcel(value as ParsedIntentInfo, parcel, 0)
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
new file mode 100644
index 0000000..411cb09
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedMainComponent
+import android.content.pm.parsing.component.ParsedService
+import android.os.Parcelable
+import java.util.Arrays
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction1
+
+@ExperimentalContracts
+abstract class ParsedMainComponentTest(kClass: KClass<out Parcelable>) :
+    ParsedComponentTest(kClass) {
+
+    final override val subclassBaseParams
+        get() = mainComponentSubclassBaseParams + listOf(
+            ParsedMainComponent::getOrder,
+            ParsedMainComponent::getProcessName,
+            ParsedMainComponent::getSplitName,
+            ParsedMainComponent::isDirectBootAware,
+            ParsedMainComponent::isEnabled,
+            ParsedMainComponent::isExported,
+        )
+
+    abstract val mainComponentSubclassBaseParams: Collection<KFunction1<*, Any?>>
+
+    final override fun subclassExtraParams() = mainComponentSubclassExtraParams() + listOf(
+        getSetByValue(
+            ParsedService::getAttributionTags,
+            ParsedService::setAttributionTags,
+            arrayOf("testAttributionTag"),
+            compare = Arrays::equals
+        ),
+    )
+
+    open fun mainComponentSubclassExtraParams(): Collection<Param?> = emptyList()
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
new file mode 100644
index 0000000..53c862a
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedPermissionGroupTest : ParsedComponentTest(ParsedPermissionGroup::class) {
+
+    override val defaultImpl = ParsedPermissionGroup()
+    override val creator = ParsedPermissionGroup.CREATOR
+
+    override val subclassBaseParams = listOf(
+        ParsedPermissionGroup::getRequestDetailResourceId,
+        ParsedPermissionGroup::getBackgroundRequestDetailResourceId,
+        ParsedPermissionGroup::getBackgroundRequestResourceId,
+        ParsedPermissionGroup::getRequestRes,
+        ParsedPermissionGroup::getPriority,
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
new file mode 100644
index 0000000..bb63e2e2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedPermission
+import android.content.pm.parsing.component.ParsedPermissionGroup
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedPermissionTest : ParsedComponentTest(ParsedPermission::class) {
+
+    override val defaultImpl = ParsedPermission()
+    override val creator = ParsedPermission.CREATOR
+
+    override val subclassExcludedMethods = listOf(
+        // Utility methods
+        "isAppOp",
+        "isRuntime",
+        "getProtection",
+        "getProtectionFlags",
+        "calculateFootprint",
+        "setKnownCert", // Tested through setKnownCerts
+    )
+    override val subclassBaseParams = listOf(
+        ParsedPermission::getBackgroundPermission,
+        ParsedPermission::getGroup,
+        ParsedPermission::getRequestRes,
+        ParsedPermission::getProtectionLevel,
+        ParsedPermission::isTree,
+    )
+
+    override fun subclassExtraParams() = listOf(
+        getter(ParsedPermission::getKnownCerts, setOf("testCert")),
+        getSetByValue(
+            ParsedPermission::getParsedPermissionGroup,
+            ParsedPermission::setParsedPermissionGroup,
+            ParsedPermissionGroup().apply { name = "test.permission.group" },
+            compare = { first, second -> equalBy(first, second, ParsedPermissionGroup::getName) }
+        ),
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
new file mode 100644
index 0000000..34f46f2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedProcess
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class) {
+
+    override val defaultImpl = ParsedProcess()
+    override val creator = ParsedProcess.CREATOR
+
+    override val excludedMethods = listOf(
+        // Copying method
+        "addStateFrom",
+    )
+
+    override val baseParams = listOf(
+        ParsedProcess::getName,
+        ParsedProcess::getGwpAsanMode,
+        ParsedProcess::getMemtagMode,
+        ParsedProcess::getNativeHeapZeroInitialized,
+    )
+
+    override fun extraParams() = listOf(
+        getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission"))
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
new file mode 100644
index 0000000..e6d5c0f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.PathPermission
+import android.content.pm.parsing.component.ParsedProvider
+import android.os.PatternMatcher
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class) {
+
+    override val defaultImpl = ParsedProvider()
+    override val creator = ParsedProvider.CREATOR
+
+    override val mainComponentSubclassBaseParams = listOf(
+        ParsedProvider::getAuthority,
+        ParsedProvider::isSyncable,
+        ParsedProvider::getReadPermission,
+        ParsedProvider::getWritePermission,
+        ParsedProvider::isGrantUriPermissions,
+        ParsedProvider::isForceUriPermissions,
+        ParsedProvider::isMultiProcess,
+        ParsedProvider::getInitOrder,
+    )
+
+    override fun mainComponentSubclassExtraParams() = listOf(
+        getSetByValue(
+            ParsedProvider::getUriPermissionPatterns,
+            ParsedProvider::setUriPermissionPatterns,
+            PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
+            transformGet = { it?.singleOrNull() },
+            transformSet = { arrayOf(it) },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    PatternMatcher::getPath,
+                    PatternMatcher::getType
+                )
+            }
+        ),
+        getSetByValue(
+            ParsedProvider::getPathPermissions,
+            ParsedProvider::setPathPermissions,
+            PathPermission(
+                "testPermissionPattern",
+                PatternMatcher.PATTERN_LITERAL,
+                "test.READ_PERMISSION",
+                "test.WRITE_PERMISSION"
+            ),
+            transformGet = { it?.singleOrNull() },
+            transformSet = { arrayOf(it) },
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    PatternMatcher::getPath,
+                    PatternMatcher::getType,
+                    PathPermission::getReadPermission,
+                    PathPermission::getWritePermission,
+                )
+            }
+        )
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
new file mode 100644
index 0000000..5530184
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -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 com.android.server.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedService
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class) {
+
+    override val defaultImpl = ParsedService()
+    override val creator = ParsedService.CREATOR
+
+    override val mainComponentSubclassBaseParams = listOf(
+        ParsedService::getForegroundServiceType,
+        ParsedService::getPermission,
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
new file mode 100644
index 0000000..1131c72
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.pm.test.parsing.parcelling
+
+import android.content.pm.parsing.component.ParsedUsesPermission
+import android.os.Parcelable
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalContracts
+class ParsedUsesPermissionTest : ParcelableComponentTest(ParsedUsesPermission::class) {
+
+    override val defaultImpl = ParsedUsesPermission("", 0)
+    override val creator = ParsedUsesPermission.CREATOR
+
+    override val baseParams = listOf(
+        ParsedUsesPermission::getName,
+        ParsedUsesPermission::getUsesPermissionFlags
+    )
+
+    override fun initialObject() = ParsedUsesPermission("", 0)
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java
new file mode 100644
index 0000000..581d2b2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithCreator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.pm.test.parsing.parcelling.java;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+public class TestSubWithCreator extends TestSuperClass {
+
+    @NonNull
+    public static final Parcelable.Creator<TestSubWithCreator> CREATOR =
+            new Parcelable.Creator<TestSubWithCreator>() {
+                @Override
+                public TestSubWithCreator createFromParcel(Parcel source) {
+                    return new TestSubWithCreator(source);
+                }
+
+                @Override
+                public TestSubWithCreator[] newArray(int size) {
+                    return new TestSubWithCreator[size];
+                }
+            };
+
+    @Nullable
+    private String subString;
+
+    public TestSubWithCreator() {
+    }
+
+    public TestSubWithCreator(@NonNull Parcel in) {
+        super(in);
+        subString = in.readString();
+    }
+
+    public void writeSubToParcel(@NonNull Parcel parcel, int flags) {
+        super.writeToParcel(parcel, flags);
+        parcel.writeString(subString);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java
new file mode 100644
index 0000000..4264a11
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSubWithoutCreator.java
@@ -0,0 +1,41 @@
+/*
+ * 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.pm.test.parsing.parcelling.java;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+public class TestSubWithoutCreator extends TestSuperClass {
+
+    @Nullable
+    private String subString;
+
+    public TestSubWithoutCreator() {
+    }
+
+    public TestSubWithoutCreator(@NonNull Parcel in) {
+        super(in);
+        subString = in.readString();
+    }
+
+    public void writeSubToParcel(@NonNull Parcel parcel, int flags) {
+        super.writeToParcel(parcel, flags);
+        parcel.writeString(subString);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
new file mode 100644
index 0000000..a009786
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
@@ -0,0 +1,118 @@
+/*
+ * 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.pm.test.parsing.parcelling.java;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+@DataClass(genGetters = true, genSetters = true, genBuilder = false, genAidl = false,
+        genParcelable = true, genConstructor = false)
+public class TestSuperClass implements Parcelable {
+
+    @Nullable
+    private String superString;
+
+    public TestSuperClass() {
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public @Nullable String getSuperString() {
+        return superString;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull TestSuperClass setSuperString(@NonNull String value) {
+        superString = value;
+        return this;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (superString != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (superString != null) dest.writeString(superString);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected TestSuperClass(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String _superString = (flg & 0x1) == 0 ? null : in.readString();
+
+        this.superString = _superString;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TestSuperClass> CREATOR
+            = new Parcelable.Creator<TestSuperClass>() {
+        @Override
+        public TestSuperClass[] newArray(int size) {
+            return new TestSuperClass[size];
+        }
+
+        @Override
+        public TestSuperClass createFromParcel(@NonNull android.os.Parcel in) {
+            return new TestSuperClass(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1624381019144L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/java/TestSuperClass.java",
+            inputSignatures = "private @android.annotation.Nullable java.lang.String superString\nclass TestSuperClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genAidl=false, genParcelable=true, genConstructor=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
new file mode 100644
index 0000000..afb18f5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.pm.test.util
+
+import com.google.common.truth.Expect
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Wrapper for [Expect] which supports ignoring any failures. This should be used with caution, but
+ * it allows a base test to be written which doesn't switch success/failure in the test itself,
+ * preventing any logic errors from causing the test to accidentally succeed.
+ */
+internal class IgnoreableExpect : TestRule {
+
+    val expect = Expect.create()
+
+    private var ignore = false
+
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                ignore = false
+                try {
+                    expect.apply(base, description).evaluate()
+                } catch (t: Throwable) {
+                    if (!ignore) {
+                        throw t
+                    }
+                }
+            }
+        }
+    }
+
+    fun ignore() {
+        ignore = true
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
new file mode 100644
index 0000000..00e3bf1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -0,0 +1,596 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.hdmi.RequestSadAction.RequestSadCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class RequestSadActionTest {
+
+    private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
+    private static final ArrayList<Integer> CODECS_TO_QUERY_1 = new ArrayList<Integer>(
+            Arrays.asList(Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
+                    Constants.AUDIO_CODEC_MPEG1, Constants.AUDIO_CODEC_MP3));
+    private static final ArrayList<Integer> CODECS_TO_QUERY_2 = new ArrayList<Integer>(
+            Arrays.asList(Constants.AUDIO_CODEC_MPEG2, Constants.AUDIO_CODEC_AAC,
+                    Constants.AUDIO_CODEC_DTS, Constants.AUDIO_CODEC_ATRAC));
+    private static final ArrayList<Integer> CODECS_TO_QUERY_3 = new ArrayList<Integer>(
+            Arrays.asList(Constants.AUDIO_CODEC_ONEBITAUDIO, Constants.AUDIO_CODEC_DDP,
+                    Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD));
+    private static final ArrayList<Integer> CODECS_TO_QUERY_4 = new ArrayList<Integer>(
+            Arrays.asList(Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_WMAPRO,
+                    Constants.AUDIO_CODEC_MAX));
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
+    private FakeNativeWrapper mNativeWrapper;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private List<byte[]> mSupportedSads;
+    private RequestSadCallback mCallback =
+            new RequestSadCallback() {
+                @Override
+                public void onRequestSadDone(
+                        List<byte[]> supportedSads) {
+                    mSupportedSads = supportedSads;
+                }
+            };
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+
+    private static byte[] concatenateSads(List<byte[]> sads) {
+        byte[] concatenatedSads = new byte[sads.size() * 3];
+        for (int i = 0; i < sads.size(); i++) {
+            for (int j = 0; j < 3; j++) {
+                concatenatedSads[3 * i + j] = sads.get(i)[j];
+            }
+        }
+        return concatenatedSads;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mMyLooper = mTestLooper.getLooper();
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+                        Collections.emptyList()) {
+                    @Override
+                    boolean isControlEnabled() {
+                        return true;
+                    }
+
+                    @Override
+                    void wakeUp() {
+                    }
+
+                    @Override
+                    protected void writeStringSystemProperty(String key, String value) {
+                        // do nothing
+                    }
+
+                    @Override
+                    boolean isPowerStandbyOrTransient() {
+                        return false;
+                    }
+
+                    @Override
+                    protected PowerManager getPowerManager() {
+                        return new PowerManager(context, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mMyLooper));
+                    }
+                };
+
+        mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
+        mHdmiCecLocalDeviceTv.init();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mLocalDevices.add(mHdmiCecLocalDeviceTv);
+        mHdmiControlService.initService();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mNativeWrapper.setPhysicalAddress(0x0000);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+    }
+
+    @Test
+    public void noResponse_queryAgain_emptyResult() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void featureAbort_dontQueryAgain_emptyResult() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
+                Constants.ABORT_INVALID_OPERAND);
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(featureAbort);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(featureAbort);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(featureAbort);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        action.processCommand(featureAbort);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void allSupported_completeResult() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_1 = new byte[]{
+                0x01, 0x18, 0x4A,
+                0x02, 0x64, 0x5A,
+                0x03, 0x4B, 0x00,
+                0x04, 0x20, 0x0A};
+        HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response1);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_2 = new byte[]{
+                0x05, 0x18, 0x4A,
+                0x06, 0x64, 0x5A,
+                0x07, 0x4B, 0x00,
+                0x08, 0x20, 0x0A};
+        HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response2);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_3 = new byte[]{
+                0x09, 0x18, 0x4A,
+                0x0A, 0x64, 0x5A,
+                0x0B, 0x4B, 0x00,
+                0x0C, 0x20, 0x0A};
+        HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response3);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_4 = new byte[]{
+                0x0D, 0x18, 0x4A,
+                0x0E, 0x64, 0x5A,
+                0x0F, 0x4B, 0x00};
+        HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        action.processCommand(response4);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(15);
+        assertThat(Arrays.equals(sadsToRespond_1,
+                concatenateSads(mSupportedSads.subList(0, 4)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_2,
+                concatenateSads(mSupportedSads.subList(4, 8)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_3,
+                concatenateSads(mSupportedSads.subList(8, 12)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_4,
+                concatenateSads(mSupportedSads.subList(12, 15)))).isTrue();
+    }
+
+    @Test
+    public void subsetSupported_subsetResult() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_1 = new byte[]{
+                0x01, 0x18, 0x4A,
+                0x03, 0x4B, 0x00,
+                0x04, 0x20, 0x0A};
+        HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response1);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_2 = new byte[]{
+                0x08, 0x20, 0x0A};
+        HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response2);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_3 = new byte[]{
+                0x09, 0x18, 0x4A,
+                0x0A, 0x64, 0x5A,
+                0x0B, 0x4B, 0x00,
+                0x0C, 0x20, 0x0A};
+        HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response3);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_4 = new byte[]{
+                0x0F, 0x4B, 0x00};
+        HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        action.processCommand(response4);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(9);
+        assertThat(Arrays.equals(sadsToRespond_1,
+                concatenateSads(mSupportedSads.subList(0, 3)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_2,
+                concatenateSads(mSupportedSads.subList(3, 4)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_3,
+                concatenateSads(mSupportedSads.subList(4, 8)))).isTrue();
+        assertThat(Arrays.equals(sadsToRespond_4,
+                concatenateSads(mSupportedSads.subList(8, 9)))).isTrue();
+    }
+
+    @Test
+    public void invalidCodecs_emptyResults() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_1 = new byte[]{
+                0x20, 0x18, 0x4A,
+                0x21, 0x64, 0x5A,
+                0x22, 0x4B, 0x00,
+                0x23, 0x20, 0x0A};
+        HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response1);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_2 = new byte[]{
+                0x24, 0x18, 0x4A,
+                0x25, 0x64, 0x5A,
+                0x26, 0x4B, 0x00,
+                0x27, 0x20, 0x0A};
+        HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response2);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_3 = new byte[]{
+                0x28, 0x18, 0x4A,
+                0x29, 0x64, 0x5A,
+                0x2A, 0x4B, 0x00,
+                0x2B, 0x20, 0x0A};
+        HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response3);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_4 = new byte[]{
+                0x2C, 0x18, 0x4A,
+                0x2D, 0x64, 0x5A,
+                0x2E, 0x4B, 0x00};
+        HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        action.processCommand(response4);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invalidMessageLength_queryAgain() {
+        RequestSadAction action = new RequestSadAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM,
+                mCallback);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_1 = new byte[]{
+                0x01, 0x18,
+                0x02, 0x64, 0x5A,
+                0x03, 0x4B, 0x00,
+                0x04, 0x20, 0x0A};
+        HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response1);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected2 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_2 = new byte[]{
+                0x05, 0x18, 0x4A,
+                0x06, 0x64, 0x5A,
+                0x07,
+                0x08, 0x20, 0x0A};
+        HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response2);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected3 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_3 = new byte[0];
+        HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_3);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response3);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expected4 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                Constants.ADDR_AUDIO_SYSTEM,
+                CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
+        byte[] sadsToRespond_4 = new byte[]{
+                0x0D, 0x18, 0x4A,
+                0x0E, 0x64, 0x5A,
+                0x0F, 0x4B};
+        HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
+                Constants.ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(), sadsToRespond_4);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        mNativeWrapper.clearResultMessages();
+        action.processCommand(response4);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mSupportedSads.size()).isEqualTo(0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index e8c5fb3..38b98ca 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -949,7 +949,7 @@
                 .addConfigPreference(new ConfigurationInfo())
                 .addReqFeature(new FeatureInfo())
                 .addFeatureGroup(new FeatureGroupInfo())
-                .setCompileSdkVersionCodename("foo23")
+                .setCompileSdkVersionCodeName("foo23")
                 .setCompileSdkVersion(100)
                 .setOverlayCategory("foo24")
                 .setOverlayIsStatic(true)
diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
index 65a9759..4a25323 100644
--- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
@@ -19,19 +19,31 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Clock;
+import java.time.ZoneOffset;
+
 /** Test that the ledger records transactions correctly. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LedgerTest {
 
+    @Before
+    public void setUp() {
+        TareUtils.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+    }
+
     @Test
     public void testInitialState() {
         final Ledger ledger = new Ledger();
@@ -72,4 +84,45 @@
         assertEquals(1, ledger.get24HourSum(1, 27 * HOUR_IN_MILLIS));
         assertEquals(0, ledger.get24HourSum(1, 28 * HOUR_IN_MILLIS));
     }
+
+    @Test
+    public void testRemoveOldTransactions() {
+        final Ledger ledger = new Ledger();
+        ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
+        assertNull(ledger.getEarliestTransaction());
+
+        final long now = getCurrentTimeMillis();
+        Ledger.Transaction transaction1 = new Ledger.Transaction(
+                now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
+        Ledger.Transaction transaction2 = new Ledger.Transaction(
+                now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
+        Ledger.Transaction transaction3 = new Ledger.Transaction(
+                now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
+        // Instant event
+        Ledger.Transaction transaction4 = new Ledger.Transaction(
+                now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
+        // Recent event
+        Ledger.Transaction transaction5 = new Ledger.Transaction(
+                now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
+        ledger.recordTransaction(transaction1);
+        ledger.recordTransaction(transaction2);
+        ledger.recordTransaction(transaction3);
+        ledger.recordTransaction(transaction4);
+        ledger.recordTransaction(transaction5);
+
+        assertEquals(transaction1, ledger.getEarliestTransaction());
+        ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
+        assertEquals(transaction2, ledger.getEarliestTransaction());
+        ledger.removeOldTransactions(23 * HOUR_IN_MILLIS);
+        assertEquals(transaction3, ledger.getEarliestTransaction());
+        // Shouldn't delete transaction3 yet since there's still a piece of it within the min age
+        // window.
+        ledger.removeOldTransactions(21 * HOUR_IN_MILLIS + 30 * MINUTE_IN_MILLIS);
+        assertEquals(transaction3, ledger.getEarliestTransaction());
+        // Instant event should be removed as soon as we hit the exact threshold.
+        ledger.removeOldTransactions(20 * HOUR_IN_MILLIS);
+        assertEquals(transaction5, ledger.getEarliestTransaction());
+        ledger.removeOldTransactions(0);
+        assertNull(ledger.getEarliestTransaction());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 41f2246..54406c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1616,6 +1616,21 @@
     }
 
     @Test
+    public void testRemoveImmediatelyWithFinishingActivity() throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
+        activity.makeFinishingLocked();
+        assertTrue(activity.finishing);
+
+        activity.getTask().removeImmediately("test");
+
+        verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken),
+                isA(DestroyActivityItem.class));
+        assertFalse(wpc.hasActivities());
+        assertEquals(DESTROYING, activity.getState());
+    }
+
+    @Test
     public void testRemoveFromHistory() {
         final ActivityRecord activity = createActivityWithTask();
         final Task rootTask = activity.getRootTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index aa56183..17288c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -53,9 +53,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -895,36 +892,6 @@
         assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
-    @Test
-    public void testAdjustImeInsetsVisibilityWhenTaskSwitchIsAnimating() {
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
-        final InsetsStateController controller = mDisplayContent.getInsetsStateController();
-        controller.getImeSourceProvider().setWindow(mImeWindow, null, null);
-
-        // Simulate app requests IME with updating all windows Insets State when IME is above app.
-        mDisplayContent.setImeLayeringTarget(app);
-        mDisplayContent.setImeInputTarget(app);
-        assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
-        controller.getImeSourceProvider().getSource().setVisible(true);
-        controller.updateAboveInsetsState(mImeWindow, false);
-
-        // Simulate task switching animation happens when switching app to app2.
-        spyOn(app);
-        spyOn(app2);
-        doReturn(true).when(app).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS);
-        doReturn(true).when(app2).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS);
-        app.mActivityRecord.mLastImeShown = true;
-
-        // Verify the IME insets is visible on app, but not for app2 during task animating.
-        InsetsState stateApp = app.getInsetsState();
-        InsetsState stateApp2 = app2.getInsetsState();
-        assertTrue(stateApp.getSource(ITYPE_IME).isVisible());
-        assertFalse(stateApp2.getSource(ITYPE_IME).isVisible());
-    }
-
     @UseTestDisplay(addWindows = { W_ACTIVITY })
     @Test
     public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 965f126..734172f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -36,6 +36,7 @@
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
+import android.media.AudioManagerInternal;
 import android.media.permission.Identity;
 import android.media.permission.PermissionUtil;
 import android.os.Binder;
@@ -44,6 +45,7 @@
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
@@ -275,6 +277,7 @@
             LocalServices.getService(PermissionManagerServiceInternal.class)
                     .setHotwordDetectionServiceProvider(null);
             mIdentity = null;
+            updateServiceUidForAudioPolicy(Process.INVALID_UID);
         }
         mCancellationTaskFuture.cancel(/* may interrupt */ true);
         if (mAudioFlinger != null) {
@@ -893,6 +896,8 @@
         connection.run(service -> service.ping(new IRemoteCallback.Stub() {
             @Override
             public void sendResult(Bundle bundle) throws RemoteException {
+                // TODO: Exit if the service has been unbound already (though there's a very low
+                // chance this happens).
                 if (DEBUG) {
                     Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
                 }
@@ -902,10 +907,21 @@
                 LocalServices.getService(PermissionManagerServiceInternal.class)
                         .setHotwordDetectionServiceProvider(() -> uid);
                 mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
+                updateServiceUidForAudioPolicy(uid);
             }
         }));
     }
 
+    private void updateServiceUidForAudioPolicy(int uid) {
+        mScheduledExecutorService.execute(() -> {
+            final AudioManagerInternal audioManager =
+                    LocalServices.getService(AudioManagerInternal.class);
+            if (audioManager != null) {
+                audioManager.setHotwordDetectionServiceUid(uid);
+            }
+        });
+    }
+
     private static void bestEffortClose(Closeable closeable) {
         try {
             closeable.close();
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
new file mode 100644
index 0000000..131611d
--- /dev/null
+++ b/tests/InputMethodStressTest/Android.bp
@@ -0,0 +1,35 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "InputMethodStressTest",
+    srcs: ["src/**/*.java"],
+    libs: ["android.test.runner"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "truth-prebuilt",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+    sdk_version: "31",
+}
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
new file mode 100644
index 0000000..e5d6518
--- /dev/null
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.inputmethod.stresstest">
+
+    <application>
+        <activity android:name=".TestActivity"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.inputmethod.stresstest">
+    </instrumentation>
+</manifest>
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
new file mode 100644
index 0000000..b194010
--- /dev/null
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<configuration description="InputMethod integration/regression test">
+    <option name="test-suite-tag" value="apct" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="InputMethodStressTest.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.inputmethod.stresstest" />
+    </test>
+</configuration>
diff --git a/tests/InputMethodStressTest/OWNERS b/tests/InputMethodStressTest/OWNERS
new file mode 100644
index 0000000..6bb4b17
--- /dev/null
+++ b/tests/InputMethodStressTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 34867
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java
new file mode 100644
index 0000000..5427fd8
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/ImeOpenCloseStressTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.inputmethod.stresstest;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.platform.test.annotations.RootPermissionTest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RootPermissionTest
+@RunWith(AndroidJUnit4.class)
+public class ImeOpenCloseStressTest {
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final int NUM_TEST_ITERATIONS = 100;
+
+    private Instrumentation mInstrumentation;
+
+    @Test
+    public void test() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(mInstrumentation.getContext(), TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        TestActivity activity = (TestActivity) mInstrumentation.startActivitySync(intent);
+        eventually(() -> assertThat(callOnMainSync(activity::hasWindowFocus)).isTrue(), TIMEOUT);
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            mInstrumentation.runOnMainSync(activity::showIme);
+            eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isTrue(), TIMEOUT);
+            mInstrumentation.runOnMainSync(activity::hideIme);
+            eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isFalse(), TIMEOUT);
+        }
+    }
+
+    private <V> V callOnMainSync(Callable<V> callable) {
+        AtomicReference<V> result = new AtomicReference<>();
+        AtomicReference<Exception> thrownException = new AtomicReference<>();
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                result.set(callable.call());
+            } catch (Exception e) {
+                thrownException.set(e);
+            }
+        });
+        if (thrownException.get() != null) {
+            throw new RuntimeException("Exception thrown from Main thread", thrownException.get());
+        }
+        return result.get();
+    }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.java b/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.java
new file mode 100644
index 0000000..7baf037
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/TestActivity.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.inputmethod.stresstest;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowInsets;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+public class TestActivity extends Activity {
+
+    private EditText mEditText;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        LinearLayout rootView = new LinearLayout(this);
+        rootView.setOrientation(LinearLayout.VERTICAL);
+        mEditText = new EditText(this);
+        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+        setContentView(rootView);
+    }
+
+    public boolean hasWindowFocus() {
+        return mEditText.hasWindowFocus();
+    }
+
+    public boolean isImeShown() {
+        WindowInsets insets = mEditText.getRootWindowInsets();
+        return insets.isVisible(WindowInsets.Type.ime());
+    }
+
+    public void showIme() {
+        mEditText.requestFocus();
+        InputMethodManager imm = getSystemService(InputMethodManager.class);
+        imm.showSoftInput(mEditText, 0);
+    }
+
+    public void hideIme() {
+        InputMethodManager imm = getSystemService(InputMethodManager.class);
+        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+    }
+}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 05c46ed..7a668a5 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -54,7 +54,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -203,194 +202,6 @@
     }
 
     /**
-     * Test that multiple available rollbacks are properly persisted.
-     */
-    @Test
-    public void testAvailableRollbackPersistence() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-
-            Uninstall.packages(TestApp.A);
-            Install.single(TestApp.A1).commit();
-            Install.single(TestApp.A2).setEnableRollback().commit();
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-            Uninstall.packages(TestApp.B);
-            Install.single(TestApp.B1).commit();
-            Install.single(TestApp.B2).setEnableRollback().commit();
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
-            // Both test apps should now be available for rollback.
-            RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A);
-            assertThat(rollbackA).isNotNull();
-            assertThat(rollbackA).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1));
-
-            RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
-            assertThat(rollbackB).isNotNull();
-            assertThat(rollbackB).packagesContainsExactly(
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Reload the persisted data.
-            rm.reloadPersistedData();
-
-            // The apps should still be available for rollback.
-            rollbackA = waitForAvailableRollback(TestApp.A);
-            assertThat(rollbackA).isNotNull();
-            assertThat(rollbackA).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1));
-
-            rollbackB = waitForAvailableRollback(TestApp.B);
-            assertThat(rollbackB).isNotNull();
-            assertThat(rollbackB).packagesContainsExactly(
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Rollback of B should not rollback A
-            RollbackUtils.rollback(rollbackB.getRollbackId());
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test that available multi-package rollbacks are properly persisted.
-     */
-    @Test
-    public void testAvailableMultiPackageRollbackPersistence() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-
-            Uninstall.packages(TestApp.A, TestApp.B);
-            Install.multi(TestApp.A1, TestApp.B1).commit();
-            Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
-
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
-            // The app should now be available for rollback.
-            RollbackInfo availableA = waitForAvailableRollback(TestApp.A);
-            assertThat(availableA).isNotNull();
-            assertThat(availableA).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            RollbackInfo availableB = waitForAvailableRollback(TestApp.B);
-            assertThat(availableB).isNotNull();
-            assertThat(availableB).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Assert they're both the same rollback
-            assertThat(availableA).hasRollbackId(availableB.getRollbackId());
-
-            // Reload the persisted data.
-            rm.reloadPersistedData();
-
-            // The apps should still be available for rollback.
-            availableA = waitForAvailableRollback(TestApp.A);
-            assertThat(availableA).isNotNull();
-            assertThat(availableA).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            availableB = waitForAvailableRollback(TestApp.B);
-            assertThat(availableB).isNotNull();
-            assertThat(availableB).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Rollback of B should rollback A as well
-            RollbackUtils.rollback(availableB.getRollbackId());
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
-
-            RollbackInfo committedA = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.A);
-            assertThat(committedA).isNotNull();
-            assertThat(committedA).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            RollbackInfo committedB = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.A);
-            assertThat(committedB).isNotNull();
-            assertThat(committedB).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Assert they're both the same rollback
-            assertThat(committedA).hasRollbackId(committedB.getRollbackId());
-            assertThat(committedA).hasRollbackId(availableA.getRollbackId());
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test that recently committed rollback data is properly persisted.
-     */
-    @Test
-    public void testRecentlyCommittedRollbackPersistence() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-
-            Uninstall.packages(TestApp.A);
-            Install.single(TestApp.A1).commit();
-            Install.single(TestApp.A2).setEnableRollback().commit();
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-            // The app should now be available for rollback.
-            RollbackInfo available = waitForAvailableRollback(TestApp.A);
-            assertThat(available).isNotNull();
-
-            // Roll back the app.
-            TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
-                    /*versionCode*/ 42, /*isApex*/ false);
-            RollbackUtils.rollback(available.getRollbackId(), cause);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
-            // Verify the recent rollback has been recorded.
-            RollbackInfo committed = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.A);
-            assertThat(committed).isNotNull();
-            assertThat(committed).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1));
-            assertThat(committed).causePackagesContainsExactly(cause);
-
-            // Reload the persisted data.
-            rm.reloadPersistedData();
-
-            // Verify the recent rollback is still recorded.
-            committed = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.A);
-            assertThat(committed).isNotNull();
-            assertThat(committed).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1));
-            assertThat(committed).causePackagesContainsExactly(cause);
-            assertThat(committed).hasRollbackId(available.getRollbackId());
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
      * Test the scheduling aspect of rollback expiration.
      */
     @Test
@@ -739,203 +550,6 @@
         }
     }
 
-    /**
-     * Test that the MANAGE_ROLLBACKS permission is required to call
-     * RollbackManager APIs.
-     */
-    @Test
-    public void testManageRollbacksPermission() throws Exception {
-        // We shouldn't be allowed to call any of the RollbackManager APIs
-        // without the MANAGE_ROLLBACKS permission.
-        RollbackManager rm = RollbackUtils.getRollbackManager();
-
-        try {
-            rm.getAvailableRollbacks();
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            rm.getRecentlyCommittedRollbacks();
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            // TODO: What if the implementation checks arguments for non-null
-            // first? Then this test isn't valid.
-            rm.commitRollback(0, Collections.emptyList(), null);
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            rm.reloadPersistedData();
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            rm.expireRollbackForPackage(TestApp.A);
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-    }
-
-    /**
-     * Test that you cannot enable rollback for a package without the
-     * MANAGE_ROLLBACKS permission.
-     */
-    @Test
-    public void testEnableRollbackPermission() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES);
-
-            Uninstall.packages(TestApp.A);
-            Install.single(TestApp.A1).commit();
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
-            Install.single(TestApp.A2).setEnableRollback().commit();
-
-            // We expect v2 of the app was installed, but rollback has not
-            // been enabled.
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-            assertThat(
-                getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test that you cannot enable rollback for a non-module package when
-     * holding the MANAGE_ROLLBACKS permission.
-     */
-    @Test
-    public void testNonModuleEnableRollback() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.MANAGE_ROLLBACKS);
-
-            Uninstall.packages(TestApp.A);
-            Install.single(TestApp.A1).commit();
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-
-            Install.single(TestApp.A2).setEnableRollback().commit();
-
-            // We expect v2 of the app was installed, but rollback has not
-            // been enabled because the test app is not a module.
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-            assertThat(
-                getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test rollback of multi-package installs is implemented.
-     */
-    @Test
-    public void testMultiPackage() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-
-            // Prep installation of the test apps.
-            Uninstall.packages(TestApp.A, TestApp.B);
-            Install.multi(TestApp.A1, TestApp.B1).commit();
-            processUserData(TestApp.A);
-            processUserData(TestApp.B);
-            Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
-            processUserData(TestApp.A);
-            processUserData(TestApp.B);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
-            // TestApp.A should now be available for rollback.
-            RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
-            assertThat(rollback).isNotNull();
-            assertThat(rollback).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            // Rollback the app. It should cause both test apps to be rolled
-            // back.
-            RollbackUtils.rollback(rollback.getRollbackId());
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
-
-            // We should see recent rollbacks listed for both A and B.
-            Thread.sleep(1000);
-            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.A);
-
-            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
-                    rm.getRecentlyCommittedRollbacks(), TestApp.B);
-            assertThat(rollback).packagesContainsExactly(
-                    Rollback.from(TestApp.A2).to(TestApp.A1),
-                    Rollback.from(TestApp.B2).to(TestApp.B1));
-
-            assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId());
-
-            processUserData(TestApp.A);
-            processUserData(TestApp.B);
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test failure to enable rollback for multi-package installs.
-     * If any one of the packages fail to enable rollback, we shouldn't enable
-     * rollback for any package.
-     */
-    @Test
-    public void testMultiPackageEnableFail() throws Exception {
-        try {
-            InstallUtils.adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-            RollbackManager rm = RollbackUtils.getRollbackManager();
-
-            Uninstall.packages(TestApp.A, TestApp.B);
-            Install.single(TestApp.A1).commit();
-            // We should fail to enable rollback here because TestApp B is not
-            // already installed.
-            Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
-
-            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
-
-            assertThat(getUniqueRollbackInfoForPackage(
-                    rm.getAvailableRollbacks(), TestApp.A)).isNull();
-            assertThat(getUniqueRollbackInfoForPackage(
-                    rm.getAvailableRollbacks(), TestApp.B)).isNull();
-        } finally {
-            InstallUtils.dropShellPermissionIdentity();
-        }
-    }
-
     @Test
     @Ignore("b/120200473")
     /**
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index ea886db..0bbde62 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -27,7 +27,7 @@
 
 static ApiVersion sDevelopmentSdkLevel = 10000;
 static const auto sDevelopmentSdkCodeNames =
-    std::unordered_set<StringPiece>({"Q", "R", "S", "Tiramisu"});
+    std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu"});
 
 static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
     {0x021c, 1},
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 5736a80..900c214 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -30,7 +30,8 @@
             CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
             CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
             CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
-            CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY
+            CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+            CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED
     )
 
     override val api: Int
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
new file mode 100644
index 0000000..641f337
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
@@ -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 com.google.android.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/**
+ * Lint Detector that finds issues with improper usages of the non-user getter methods of Settings
+ */
+@Suppress("UnstableApiUsage")
+class CallingSettingsNonUserGetterMethodsDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames(): List<String> = listOf(
+            "getString",
+            "getInt",
+            "getLong",
+            "getFloat"
+    )
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        val evaluator = context.evaluator
+        if (evaluator.isMemberInClass(method, "android.provider.Settings.Secure") ||
+                evaluator.isMemberInClass(method, "android.provider.Settings.System")
+        ) {
+            val message = getIncidentMessageNonUserGetterMethods(getMethodSignature(method))
+            context.report(ISSUE_NON_USER_GETTER_CALLED, node, context.getLocation(node), message)
+        }
+    }
+
+    private fun getMethodSignature(method: PsiMethod) =
+            method.containingClass
+                    ?.qualifiedName
+                    ?.let { "$it#${method.name}" }
+                    ?: method.name
+
+    companion object {
+        @JvmField
+        val ISSUE_NON_USER_GETTER_CALLED: Issue = Issue.create(
+                id = "NonUserGetterCalled",
+                briefDescription = "Non-ForUser Getter Method called to Settings",
+                explanation = """
+                    System process should not call the non-ForUser getter methods of \
+                    `Settings.Secure` or `Settings.System`. For example, instead of \
+                    `Settings.Secure.getInt()`, use `Settings.Secure.getIntForUser()` instead. \
+                    This will make sure that the correct Settings value is retrieved.
+                    """,
+                category = Category.CORRECTNESS,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingSettingsNonUserGetterMethodsDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageNonUserGetterMethods(methodSignature: String) =
+                "`$methodSignature()` called from system process. " +
+                        "Please call `${methodSignature}ForUser()` instead. "
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
new file mode 100644
index 0000000..1034029
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class CallingSettingsNonUserGetterMethodsIssueDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = CallingSettingsNonUserGetterMethodsDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun testDoesNotDetectIssues() {
+        lint().files(
+                java(
+                        """
+                    package test.pkg;
+                    import android.provider.Settings.Secure;
+                    public class TestClass1 {
+                        private void testMethod(Context context) {
+                            final int value = Secure.getIntForUser(context.getContentResolver(),
+                                Settings.Secure.KEY1, 0, 0);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    fun testDetectsNonUserGetterCalledFromSecure() {
+        lint().files(
+                java(
+                        """
+                    package test.pkg;
+                    import android.provider.Settings.Secure;
+                    public class TestClass1 {
+                        private void testMethod(Context context) {
+                            final int value = Secure.getInt(context.getContentResolver(),
+                                Settings.Secure.KEY1);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: \
+                        android.provider.Settings.Secure#getInt() called from system process. \
+                        Please call android.provider.Settings.Secure#getIntForUser() instead.  \
+                        [NonUserGetterCalled]
+                                final int value = Secure.getInt(context.getContentResolver(),
+                                                  ^
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+    fun testDetectsNonUserGetterCalledFromSystem() {
+        lint().files(
+                java(
+                        """
+                    package test.pkg;
+                    import android.provider.Settings.System;
+                    public class TestClass1 {
+                        private void testMethod(Context context) {
+                            final float value = System.getFloat(context.getContentResolver(),
+                                Settings.System.KEY1);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: \
+                        android.provider.Settings.System#getFloat() called from system process. \
+                        Please call android.provider.Settings.System#getFloatForUser() instead.  \
+                        [NonUserGetterCalled]
+                                final float value = System.getFloat(context.getContentResolver(),
+                                                    ^
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsNonUserGetterCalledFromSettings() {
+        lint().files(
+                java(
+                        """
+                    package test.pkg;
+                    import android.provider.Settings;
+                    public class TestClass1 {
+                        private void testMethod(Context context) {
+                            float value = Settings.System.getFloat(context.getContentResolver(),
+                                Settings.System.KEY1);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: \
+                        android.provider.Settings.System#getFloat() called from system process. \
+                        Please call android.provider.Settings.System#getFloatForUser() instead.  \
+                        [NonUserGetterCalled]
+                                float value = Settings.System.getFloat(context.getContentResolver(),
+                                              ^
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsNonUserGettersCalledFromSystemAndSecure() {
+        lint().files(
+                java(
+                        """
+                    package test.pkg;
+                    import android.provider.Settings.Secure;
+                    import android.provider.Settings.System;
+                    public class TestClass1 {
+                        private void testMethod(Context context) {
+                            final long value1 = Secure.getLong(context.getContentResolver(),
+                                Settings.Secure.KEY1, 0);
+                            final String value2 = System.getString(context.getContentResolver(),
+                                Settings.System.KEY2);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:6: Warning: \
+                        android.provider.Settings.Secure#getLong() called from system process. \
+                        Please call android.provider.Settings.Secure#getLongForUser() instead.  \
+                        [NonUserGetterCalled]
+                                final long value1 = Secure.getLong(context.getContentResolver(),
+                                                    ^
+                        src/test/pkg/TestClass1.java:8: Warning: \
+                        android.provider.Settings.System#getString() called from system process. \
+                        Please call android.provider.Settings.System#getStringForUser() instead.  \
+                        [NonUserGetterCalled]
+                                final String value2 = System.getString(context.getContentResolver(),
+                                                      ^
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    private val SettingsStub: TestFile = java(
+            """
+            package android.provider;
+            public class Settings {
+                public class Secure {
+                    float getFloat(ContentResolver cr, String key) {
+                        return 0.0f;
+                    }
+                    long getLong(ContentResolver cr, String key) {
+                        return 0l;
+                    }
+                    int getInt(ContentResolver cr, String key) {
+                        return 0;
+                    }
+                }
+                public class System {
+                    float getFloat(ContentResolver cr, String key) {
+                        return 0.0f;
+                    }
+                    long getLong(ContentResolver cr, String key) {
+                        return 0l;
+                    }
+                    String getString(ContentResolver cr, String key) {
+                        return null;
+                    }
+                }
+            }
+            """
+    ).indented()
+
+    private val stubs = arrayOf(SettingsStub)
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}